diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e195cfd64..de0ba909c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,10 +21,6 @@ - **file.ext:** Description of what was changed and why -## Screenshots - - - ## Before you submit -- Please add **unit tests** where it makes sense to do so (encouraged but not required) +- Please add **unit tests** where it makes sense to do so diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..eb34abf9c --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,4 @@ +ignore: + - "crates/sdk-schemas" # Tool + - "crates/uniffi-bindgen" # Tool + - "crates/memory-testing" # Testing diff --git a/.github/renovate.json b/.github/renovate.json index 71e131e43..0e2492512 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,34 +1,9 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base", - "schedule:weekends", - ":combinePatchMinorReleases", - ":dependencyDashboard", - ":maintainLockFilesWeekly", - ":prConcurrentLimit10", - ":rebaseStalePrs", - ":separateMajorReleases" - ], + "extends": ["github>bitwarden/renovate-config:non-pinned"], "separateMajorMinor": true, - "enabledManagers": ["cargo", "github-actions", "npm", "nuget"], + "enabledManagers": ["cargo", "dockerfile", "github-actions", "npm", "nuget"], "packageRules": [ - { - "groupName": "npm minor", - "matchManagers": ["npm"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "matchManagers": ["cargo"], - "matchUpdateTypes": ["patch"], - "enabled": false - }, - { - "matchManagers": ["cargo"], - "matchUpdateTypes": ["minor"], - "matchCurrentVersion": ">=1.0.0", - "enabled": false - }, { "matchManagers": ["cargo"], "matchPackagePatterns": ["pyo3*"], @@ -36,8 +11,8 @@ "groupName": "pyo3 non-major" }, { - "groupName": "nuget minor", - "matchManagers": ["nuget"], + "groupName": "dockerfile minor", + "matchManagers": ["dockerfile"], "matchUpdateTypes": ["minor", "patch"] }, { @@ -45,5 +20,6 @@ "matchManagers": ["github-actions"], "matchUpdateTypes": ["minor", "patch"] } - ] + ], + "ignoreDeps": ["dotnet-sdk"] } diff --git a/.github/secrets/devid-app-cert.p12.gpg b/.github/secrets/devid-app-cert.p12.gpg new file mode 100644 index 000000000..8e2e2146e Binary files /dev/null and b/.github/secrets/devid-app-cert.p12.gpg differ diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 4fd1cc975..c6b2ae5c5 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" workflow_dispatch: defaults: @@ -25,20 +25,20 @@ jobs: - target: i686-linux-android steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: - toolchain: 1.67.0 # https://github.com/cross-rs/cross/issues/1222 + toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 with: key: ${{ matrix.settings.target }}-cargo - name: Install Cross - run: cargo install cross + run: cargo install cross --locked --git https://github.com/cross-rs/cross.git --rev 185398b1b885820515a212de720a306b08e2c8c9 - name: Build env: @@ -46,7 +46,7 @@ jobs: run: cross build -p bitwarden-uniffi --release --target=${{ matrix.settings.target }} - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: android-${{ matrix.settings.target }} path: ./target/${{ matrix.settings.target }}/release/libbitwarden_uniffi.so @@ -57,36 +57,36 @@ jobs: needs: build steps: - name: Checkout repo (PR) - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 if: github.event_name == 'pull_request' with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} - name: Checkout repo (Push) - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 if: github.event_name == 'push' with: fetch-depth: 0 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 with: key: cargo-combine-cache - name: Setup Java - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 with: distribution: temurin java-version: 17 - name: Download Artifacts - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 - name: Move artifacts working-directory: languages/kotlin/sdk/src/main/jniLibs @@ -102,7 +102,7 @@ jobs: run: ./build-schemas.sh - name: Publish - uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0 + uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0 with: arguments: sdk:publish build-root-directory: languages/kotlin diff --git a/.github/workflows/build-cli-docker.yml b/.github/workflows/build-cli-docker.yml new file mode 100644 index 000000000..19c5da2ca --- /dev/null +++ b/.github/workflows/build-cli-docker.yml @@ -0,0 +1,157 @@ +--- +name: Build bws Docker image + +on: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + workflow_dispatch: + pull_request: + +env: + _AZ_REGISTRY: bitwardenprod.azurecr.io + +jobs: + build-docker: + name: Build Docker image + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - 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 + echo "is_publish_branch=true" >> $GITHUB_ENV + else + echo "is_publish_branch=false" >> $GITHUB_ENV + fi + + ########## Set up Docker ########## + - name: Set up QEMU emulators + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + ########## Login to Docker registries ########## + - name: Login to Azure - Prod Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + + - name: Login to Azure ACR + run: az acr login -n ${_AZ_REGISTRY%.azurecr.io} + + - 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: Setup Docker Trust + if: ${{ env.is_publish_branch == 'true' }} + uses: bitwarden/gh-actions/setup-docker-trust@main + with: + azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + azure-keyvault-name: "bitwarden-ci" + + ########## Generate image tag and build Docker image ########## + - name: Generate Docker image tag + id: tag + 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 + IMAGE_TAG=dev + elif [[ ("${IMAGE_TAG}" == "rc") || ("${IMAGE_TAG}" == "hotfix-rc") ]]; then + IMAGE_TAG=rc + fi + + echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT + + - name: Generate tag list + 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 + 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@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + with: + context: . + file: crates/bws/Dockerfile + platforms: | + linux/amd64, + linux/arm64/v8 + push: true + tags: ${{ steps.tag-list.outputs.tags }} + secrets: | + "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" + + - name: Log out of Docker and disable Docker Notary + if: ${{ env.is_publish_branch == 'true' }} + run: | + docker logout + echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV + + check-failures: + name: Check for failures + if: always() + runs-on: ubuntu-22.04 + 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' + env: + BUILD_DOCKER_STATUS: ${{ needs.build-docker.result }} + run: | + if [ "$BUILD_DOCKER_STATUS" = "failure" ]; then + exit 1 + fi + + - name: Login to Azure - CI subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + if: failure() + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + if: failure() + with: + keyvault: "bitwarden-ci" + secrets: "devops-alerts-slack-webhook-url" + + - name: Notify Slack on failure + uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0 + if: failure() + env: + SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} + with: + status: ${{ job.status }} diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 624a69c4f..37f5c15c8 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" workflow_dispatch: @@ -20,9 +20,10 @@ jobs: runs-on: ubuntu-22.04 outputs: package_version: ${{ steps.retrieve-version.outputs.package_version }} + sign: ${{ steps.sign.outputs.sign }} steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Get Package Version id: retrieve-version @@ -30,7 +31,105 @@ jobs: VERSION=$(grep -o '^version = ".*"' crates/bws/Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") echo "package_version=$VERSION" >> $GITHUB_OUTPUT - build: + - name: Sign if repo is owned by Bitwarden + id: sign + env: + REPO_OWNER: ${{ github.repository_owner }} + run: | + if [[ $REPO_OWNER == bitwarden ]]; then + echo "sign=true" >> $GITHUB_OUTPUT + fi + echo "sign=false" >> $GITHUB_OUTPUT + + build-windows: + name: Building CLI for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} + runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} + needs: setup + env: + _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} + strategy: + fail-fast: false + matrix: + settings: + - os: windows-2022 + target: x86_64-pc-windows-msvc + + - os: windows-2022 + target: aarch64-pc-windows-msvc + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Install rust + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable + with: + toolchain: stable + targets: ${{ matrix.settings.target }} + + - name: Cache cargo registry + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + with: + key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} + + - name: Build + env: + TARGET: ${{ matrix.settings.target }} + run: cargo build ${{ matrix.features }} -p bws --release --target=${{ matrix.settings.target }} + + - name: Login to Azure + if: ${{ needs.setup.outputs.sign == 'true' }} + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + if: ${{ needs.setup.outputs.sign == 'true' }} + id: retrieve-secrets-windows + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "code-signing-vault-url, + code-signing-client-id, + code-signing-tenant-id, + code-signing-client-secret, + code-signing-cert-name" + + - name: Install AST + if: ${{ needs.setup.outputs.sign == 'true' }} + run: dotnet tool install --global AzureSignTool --version 4.0.1 + + - name: Sign windows binary + if: ${{ needs.setup.outputs.sign == 'true' }} + env: + SIGNING_VAULT_URL: ${{ steps.retrieve-secrets-windows.outputs.code-signing-vault-url }} + SIGNING_CLIENT_ID: ${{ steps.retrieve-secrets-windows.outputs.code-signing-client-id }} + SIGNING_TENANT_ID: ${{ steps.retrieve-secrets-windows.outputs.code-signing-tenant-id }} + SIGNING_CLIENT_SECRET: ${{ steps.retrieve-secrets-windows.outputs.code-signing-client-secret }} + SIGNING_CERT_NAME: ${{ steps.retrieve-secrets-windows.outputs.code-signing-cert-name }} + run: | + azuresigntool sign -v \ + -kvu $SIGNING_VAULT_URL \ + -kvi $SIGNING_CLIENT_ID \ + -kvt $SIGNING_TENANT_ID \ + -kvs $SIGNING_CLIENT_SECRET \ + -kvc $SIGNING_CERT_NAME \ + -fd sha256 \ + -du https://bitwarden.com \ + -tr http://timestamp.digicert.com \ + ./target/${{ matrix.settings.target }}/release/bws.exe + + - name: Zip + shell: cmd + run: 7z a ./bws-${{ matrix.settings.target }}-%_PACKAGE_VERSION%.zip ./target/${{ matrix.settings.target }}/release/bws.exe + + - name: Upload artifact + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + with: + name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip + path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip + if-no-files-found: error + + build-macos: name: Building CLI for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} needs: @@ -47,35 +146,133 @@ jobs: - os: macos-12 target: aarch64-apple-darwin - - os: windows-2022 - target: x86_64-pc-windows-msvc + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - os: windows-2022 - target: aarch64-pc-windows-msvc + - name: Install rust + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable + with: + toolchain: stable + targets: ${{ matrix.settings.target }} + + - name: Cache cargo registry + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + with: + key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} + + - name: Build + env: + TARGET: ${{ matrix.settings.target }} + run: cargo build ${{ matrix.features }} -p bws --release --target=${{ matrix.settings.target }} + + - name: Login to Azure + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets macos + id: retrieve-secrets-macos + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "macos-bws-notarization-apple-id, + macos-bws-notarization-team-id, + macos-bws-notarization-password, + macos-bws-certificate-name, + macos-bws-installer-certificate-name" + + - name: Decrypt secrets + env: + DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }} + run: | + mkdir -p $HOME/secrets + + gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ + --output "$HOME/secrets/devid-app-cert.p12" \ + "$GITHUB_WORKSPACE/.github/secrets/devid-app-cert.p12.gpg" + + - name: Set up keychain + env: + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }} + run: | + security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security set-keychain-settings -lut 1200 build.keychain + + ls $HOME/secrets + + security import "$HOME/secrets/devid-app-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \ + -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild + + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - - os: ubuntu-22.04 + - name: Sign macos + 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 ./target/${{ matrix.settings.target }}/release/bws + + - name: Notarize app macos + env: + MACOS_NOTARIZATION_APPLE_ID: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-notarization-apple-id }} + MACOS_NOTARIZATION_TEAM_ID: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-notarization-team-id }} + MACOS_NOTARIZATION_PWD: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-notarization-password }} + MACOS_CERTIFICATE_NAME: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-certificate-name }} + run: | + echo "Create keychain profile" + 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-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip ./target/${{ matrix.settings.target }}/release/bws + + codesign --sign "$MACOS_CERTIFICATE_NAME" --verbose=3 --force --options=runtime --timestamp ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip + + echo "Notarize app" + xcrun notarytool submit ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip --keychain-profile "notarytool-profile" --wait + + - name: Upload artifact + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + with: + name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip + path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip + if-no-files-found: error + + build-linux: + name: Building CLI for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} + runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} + needs: + - setup + env: + _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} + strategy: + fail-fast: false + matrix: + settings: + - os: ubuntu-20.04 target: x86_64-unknown-linux-gnu - - os: ubuntu-22.04 + - os: ubuntu-20.04 target: aarch64-unknown-linux-gnu steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 with: key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} - name: Install Cross (aarch64-unknown-linux-gnu) if: ${{ matrix.settings.target == 'aarch64-unknown-linux-gnu' }} - run: cargo install cross + run: cargo install cross --locked --git https://github.com/cross-rs/cross.git --rev 185398b1b885820515a212de720a306b08e2c8c9 - name: Build if: ${{ matrix.settings.target != 'aarch64-unknown-linux-gnu' }} @@ -89,17 +286,11 @@ jobs: TARGET: ${{ matrix.settings.target }} run: cross build ${{ matrix.features }} -p bws --release --target=${{ matrix.settings.target }} - - name: Zip Windows - shell: cmd - if: runner.os == 'Windows' - run: 7z a ./bws-${{ matrix.settings.target }}-%_PACKAGE_VERSION%.zip ./target/${{ matrix.settings.target }}/release/bws.exe - - - name: Zip Unix - if: runner.os != 'Windows' + - 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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip @@ -110,20 +301,20 @@ jobs: runs-on: macos-12 needs: - setup - - build + - build-macos env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download x86_64-apple-darwin artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 with: name: bws-x86_64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip - name: Download aarch64-apple-darwin artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 with: name: bws-aarch64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip @@ -138,11 +329,73 @@ jobs: lipo -create -output ./bws-macos-universal/bws ./bws-x86_64-apple-darwin/bws ./bws-aarch64-apple-darwin/bws - - name: Zip universal artifact - run: zip ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip ./bws-macos-universal/bws + - name: Login to Azure + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve secrets + id: retrieve-secrets-macos + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "macos-bws-notarization-apple-id, + macos-bws-notarization-team-id, + macos-bws-notarization-password, + macos-bws-certificate-name, + macos-bws-installer-certificate-name" + + - name: Decrypt secrets + env: + DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }} + run: | + mkdir -p $HOME/secrets + + gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ + --output "$HOME/secrets/devid-app-cert.p12" \ + "$GITHUB_WORKSPACE/.github/secrets/devid-app-cert.p12.gpg" + + - name: Set up keychain + env: + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }} + run: | + security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security set-keychain-settings -lut 1200 build.keychain + + security import "$HOME/secrets/devid-app-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \ + -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild + + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + + - 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 + + - name: Notarize app + env: + MACOS_NOTARIZATION_APPLE_ID: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-notarization-apple-id }} + MACOS_NOTARIZATION_TEAM_ID: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-notarization-team-id }} + MACOS_NOTARIZATION_PWD: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-notarization-password }} + MACOS_CERTIFICATE_NAME: ${{ steps.retrieve-secrets-macos.outputs.macos-bws-certificate-name }} + run: | + + echo "Create keychain profile" + 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 + + codesign --sign "$MACOS_CERTIFICATE_NAME" --verbose=3 --force --options=runtime --timestamp ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip + + echo "Notarize app" + xcrun notarytool submit ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip --keychain-profile "notarytool-profile" --wait - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip path: ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip @@ -155,15 +408,15 @@ jobs: - setup steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 with: key: cargo-cli-about @@ -177,7 +430,7 @@ jobs: sed -i.bak 's/\$NAME\$/Bitwarden Secrets Manager CLI/g' THIRDPARTY.html - name: Upload artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: THIRDPARTY.html path: ./crates/bws/THIRDPARTY.html diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 608e8c47d..b08b37160 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -1,9 +1,13 @@ name: Build .NET SDK on: - pull_request: + push: branches: - - master + - main + - rc + - hotfix-rc + pull_request: + workflow_dispatch: jobs: generate_schemas: @@ -12,67 +16,84 @@ jobs: build_rust: uses: ./.github/workflows/build-rust-cross-platform.yml + version: + name: Get version + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - 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 + build_dotnet: name: Build .NET runs-on: ubuntu-22.04 needs: - generate_schemas - build_rust + - version steps: - name: Checkout Repository - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download C# schemas artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 with: name: schemas.cs path: languages/csharp/Bitwarden.Sdk - name: Set up .NET Core - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 with: global-json-file: languages/csharp/global.json - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 with: name: libbitwarden_c_files-x86_64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-x64 - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 with: name: libbitwarden_c_files-aarch64-apple-darwin path: languages/csharp/Bitwarden.Sdk/macos-arm64 - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 with: name: libbitwarden_c_files-x86_64-unknown-linux-gnu - path: languages/csharp/Bitwarden.Sdk/ubuntu-x64 + path: languages/csharp/Bitwarden.Sdk/linux-x64 - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 with: name: libbitwarden_c_files-x86_64-pc-windows-msvc path: languages/csharp/Bitwarden.Sdk/windows-x64 - - name: Build .NET 6 Project + - name: Build .NET Project working-directory: languages/csharp/Bitwarden.Sdk run: | dotnet restore dotnet build --configuration Release - name: Pack NuGet Package - env: - VERSION: 0.0.1 - run: dotnet pack --configuration Release -p:PackageID=Bitwarden.Sdk -p:Version=${VERSION} --output ./nuget-output /nologo /v:n + run: dotnet pack --configuration Release --output ./nuget-output /nologo /v:n working-directory: languages/csharp/Bitwarden.Sdk - name: Upload NuGet package - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: - name: Bitwarden.Sdk.0.0.1.nupkg + name: Bitwarden.Sdk.${{ needs.version.outputs.version }}.nupkg path: | ./languages/csharp/Bitwarden.Sdk/nuget-output/*.nupkg diff --git a/.github/workflows/build-go.yaml b/.github/workflows/build-go.yaml new file mode 100644 index 000000000..433013aac --- /dev/null +++ b/.github/workflows/build-go.yaml @@ -0,0 +1,49 @@ +name: Build Go SDK + +on: + push: + branches: + - main + - rc + - hotfix-rc + + pull_request: + +env: + GO111MODULE: on + GO_VERSION: "^1.18" + +jobs: + build: + name: Build + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Go environment + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Cache dependencies + uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: npm ci + run: npm ci + + - name: Generate schemas + run: npm run schemas + + - name: Build + working-directory: languages/go + run: go build -v ./... + + - name: Test + working-directory: languages/go + run: go test -v ./... diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml new file mode 100644 index 000000000..3b3c4ba5a --- /dev/null +++ b/.github/workflows/build-java.yml @@ -0,0 +1,69 @@ +name: Build Java SDK + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + generate_schemas: + uses: ./.github/workflows/generate_schemas.yml + + build_rust: + uses: ./.github/workflows/build-rust-cross-platform.yml + + build_java: + name: Build Java + runs-on: ubuntu-22.04 + needs: + - generate_schemas + - build_rust + + steps: + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Download Java schemas artifact + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + with: + name: sdk-schemas-java + path: languages/java/src/main/java/bit/sdk/schema/ + + - name: Setup Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 + with: + distribution: temurin + java-version: 17 + + - name: Download x86_64-apple-darwin files + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + 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@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + with: + name: libbitwarden_c_files-aarch64-apple-darwin + path: languages/java/src/main/resources/darwin-aarch64 + + - name: Download x86_64-unknown-linux-gnu files + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + 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@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + with: + name: libbitwarden_c_files-x86_64-pc-windows-msvc + path: languages/java/src/main/resources/win32-x86-64 + + - name: Publish Maven + uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0 + with: + arguments: publish + build-root-directory: languages/java + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index b55ef939f..3bf8f2add 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" workflow_dispatch: @@ -51,28 +51,28 @@ jobs: strip *.node steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Node - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: "npm" cache-dependency-path: crates/bitwarden-napi/package-lock.json - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 with: key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} - name: Retrieve schemas - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 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 new file mode 100644 index 000000000..9578a42d6 --- /dev/null +++ b/.github/workflows/build-python-wheels.yml @@ -0,0 +1,122 @@ +--- +name: Build Python Wheels + +on: + pull_request: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + workflow_dispatch: + +defaults: + run: + shell: bash + working-directory: languages/python + +jobs: + generate_schemas: + uses: ./.github/workflows/generate_schemas.yml + + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + package_version: ${{ steps.retrieve-version.outputs.package_version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Get Package Version + id: retrieve-version + run: | + VERSION="$(grep -o '^version = ".*"' ../../crates/bitwarden-py/Cargo.toml | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")" + echo "package_version=$VERSION" >> $GITHUB_OUTPUT + + build: + name: Building Python wheel for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} + runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} + needs: + - generate_schemas + - setup + env: + _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} + strategy: + fail-fast: false + matrix: + settings: + - os: macos-12 + target: x86_64-apple-darwin + + - os: macos-12 + target: aarch64-apple-darwin + + - os: windows-2022 + target: x86_64-pc-windows-msvc + + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + + - os: ubuntu-22.04 + target: aarch64-unknown-linux-gnu + + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: 18 + + - name: Install rust + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable + with: + toolchain: stable + targets: ${{ matrix.settings.target }} + + - name: Cache cargo registry + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} + + - name: Retrieve schemas + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + 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@a3013db91b2ef2e51420cfe99ee619c8e72a17e6 # v1.40.8 + with: + target: ${{ matrix.settings.target }} + args: --release --find-interpreter --sdist + sccache: "true" + manylinux: "2_28" # https://github.com/pola-rs/polars/pull/12211 + working-directory: ${{ github.workspace }}/languages/python + + - name: Build wheels (Linux - x86_64) + if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' }} + uses: PyO3/maturin-action@a3013db91b2ef2e51420cfe99ee619c8e72a17e6 # v1.40.8 + with: + target: ${{ matrix.settings.target }} + args: --release --find-interpreter --sdist + container: quay.io/pypa/manylinux_2_28_x86_64:2023-11-20-745eb52 + sccache: "true" + manylinux: "2_28" # https://github.com/pola-rs/polars/pull/12211 + working-directory: ${{ github.workspace }}/languages/python + + - name: Upload wheels + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + 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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: bitwarden_sdk-${{ env._PACKAGE_VERSION }}-sdist + path: ${{ github.workspace }}/target/wheels/bitwarden_sdk-*.tar.gz diff --git a/.github/workflows/build-rust-crates.yml b/.github/workflows/build-rust-crates.yml index 0b868367d..09952a126 100644 --- a/.github/workflows/build-rust-crates.yml +++ b/.github/workflows/build-rust-crates.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" @@ -31,19 +31,21 @@ jobs: - bitwarden - bitwarden-api-api - bitwarden-api-identity + - bitwarden-crypto + - bitwarden-generators steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Build run: cargo build -p ${{ matrix.package }} --release @@ -59,21 +61,21 @@ jobs: release-dry-run: name: Release dry-run runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/head/master' || github.ref == 'refs/head/rc' || github.ref == 'refs/head/hotfix-rc' }} + if: ${{ github.ref == 'refs/head/main' || github.ref == 'refs/head/rc' || github.ref == 'refs/head/hotfix-rc' }} needs: - build steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Install cargo-release run: cargo install cargo-release diff --git a/.github/workflows/build-rust-cross-platform.yml b/.github/workflows/build-rust-cross-platform.yml index 007cf5d17..ae745cbde 100644 --- a/.github/workflows/build-rust-cross-platform.yml +++ b/.github/workflows/build-rust-cross-platform.yml @@ -2,6 +2,13 @@ name: Build Rust Cross Platform on: workflow_call: + workflow_dispatch: + push: + branches: + - main + - rc + - hotfix-rc + pull_request: jobs: build_rust: @@ -22,15 +29,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Add build architecture run: rustup target add ${{ matrix.settings.target }} @@ -41,7 +48,7 @@ jobs: run: cargo build --target ${{ matrix.settings.target }} --release - name: Upload Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: libbitwarden_c_files-${{ matrix.settings.target }} path: | diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml new file mode 100644 index 000000000..87e14d2e2 --- /dev/null +++ b/.github/workflows/build-wasm.yml @@ -0,0 +1,76 @@ +--- +name: Build @bitwarden/sdk-wasm + +on: + pull_request: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + workflow_dispatch: + +defaults: + run: + shell: bash + working-directory: crates/bitwarden-wasm + +jobs: + build: + name: Building @bitwarden/sdk-wasm + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: 18 + registry-url: "https://npm.pkg.github.com" + cache: "npm" + + - name: Install dependencies + run: npm i -g binaryen + + - name: Install rust + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # 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 + + - name: Build + run: ./build.sh -r + + - name: Upload artifact + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: sdk-bitwarden-wasm + path: ${{ github.workspace }}/languages/js/wasm/* + if-no-files-found: error + + - name: Set version + if: ${{ github.ref == 'refs/heads/main' }} + # Fetches current version from registry and uses prerelease to bump it + run: | + npm version --no-git-tag-version $(npm view @bitwarden/sdk-wasm@latest version) + npm version --no-git-tag-version prerelease + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + working-directory: languages/js/wasm + + - name: Publish NPM + if: ${{ github.ref == 'refs/heads/main' }} + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + working-directory: languages/js/wasm diff --git a/.github/workflows/cloc.yml b/.github/workflows/cloc.yml index 48f72ff65..f87ba6aa8 100644 --- a/.github/workflows/cloc.yml +++ b/.github/workflows/cloc.yml @@ -3,7 +3,7 @@ name: CLOC on: workflow_dispatch: push: - branches: ["master"] + branches: ["main"] pull_request: jobs: @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up cloc run: | diff --git a/.github/workflows/delete-old-packages.yml b/.github/workflows/delete-old-packages.yml index 517560ede..b3be807a6 100644 --- a/.github/workflows/delete-old-packages.yml +++ b/.github/workflows/delete-old-packages.yml @@ -15,11 +15,11 @@ jobs: name: Cleanup Android SDK runs-on: ubuntu-22.04 steps: - - uses: actions/delete-package-versions@0d39a63126868f5eefaa47169615edd3c0f61e20 # v4.1.1 + - uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 with: package-name: com.bitwarden.sdk-android package-type: maven min-versions-to-keep: 25 # Ignore versions only containing version numbers - ignore-versions: '^\\d*\\.\\d*\\.\\d*$' + ignore-versions: '^\d*\.\d*\.\d*(-SNAPSHOT)?$' diff --git a/.github/workflows/direct-minimal-versions.yml b/.github/workflows/direct-minimal-versions.yml index 579817c49..3340cd767 100644 --- a/.github/workflows/direct-minimal-versions.yml +++ b/.github/workflows/direct-minimal-versions.yml @@ -5,7 +5,7 @@ on: pull_request: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" workflow_dispatch: @@ -36,16 +36,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: nightly targets: ${{ matrix.settings.target }} - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 with: key: dmv-${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 9b2ac57c3..6513ee998 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -2,6 +2,12 @@ name: Generate schemas on: workflow_call: + workflow_dispatch: + push: + branches: + - main + - rc + - hotfix-rc env: CARGO_TERM_COLOR: always @@ -13,15 +19,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Set up Node - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: cache: "npm" cache-dependency-path: "package-lock.json" @@ -31,35 +37,55 @@ jobs: run: npm ci - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: NPM Schemas run: npm run schemas - name: Upload ts schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: schemas.ts - path: ${{ github.workspace }}/languages/js_webassembly/bitwarden_client/schemas.ts + path: ${{ github.workspace }}/languages/js/sdk-client/src/schemas.ts if-no-files-found: error - name: Upload c# schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: schemas.cs path: ${{ github.workspace }}/languages/csharp/Bitwarden.Sdk/schemas.cs if-no-files-found: error - name: Upload python schemas artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: schemas.py - path: ${{ github.workspace }}/languages/python/BitwardenClient/schemas.py + path: ${{ github.workspace }}/languages/python/bitwarden_sdk/schemas.py + if-no-files-found: error + + - name: Upload ruby schemas artifact + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + 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@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: sdk-schemas-json path: ${{ github.workspace }}/support/schemas/* if-no-files-found: error + + - name: Upload Go schemas artifact + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: schemas.go + path: ${{ github.workspace }}/languages/go/schema.go + + - name: Upload java schemas artifact + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: sdk-schemas-java + path: ${{ github.workspace }}/languages/java/src/main/java/com/bitwarden/sdk/schema/* + if-no-files-found: error diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 425f4e83a..cb35c99c1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,7 +3,7 @@ name: Lint on: workflow_dispatch: push: - branches: ["master"] + branches: ["main"] pull_request: env: @@ -17,21 +17,40 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable + - name: Install rust nightly + run: | + rustup toolchain install nightly + rustup component add rustfmt --toolchain nightly-x86_64-unknown-linux-gnu + - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Cargo fmt - run: cargo fmt --check + run: cargo +nightly fmt --check + + - name: Install clippy-sarif and sarif-fmt + run: cargo install clippy-sarif sarif-fmt --locked --git https://github.com/psastras/sarif-rs.git --rev 11c33a53f6ffeaed736856b86fb6b7b09fabdfd8 + + - name: Cargo clippy + run: cargo clippy --all-features --tests --message-format=json | + clippy-sarif | tee clippy_result.sarif | sarif-fmt + env: + RUSTFLAGS: "-D warnings" + + - name: Upload Clippy results to GitHub + uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + with: + sarif_file: clippy_result.sarif - name: Set up Node - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: cache: "npm" cache-dependency-path: "package-lock.json" @@ -47,8 +66,3 @@ jobs: run: cargo doc --no-deps --features internal env: RUSTDOCFLAGS: "-D warnings" - - - name: Cargo clippy - run: cargo clippy --all-features - env: - RUSTFLAGS: "-D warnings" diff --git a/.github/workflows/memory-testing.yml b/.github/workflows/memory-testing.yml new file mode 100644 index 000000000..d8b6db002 --- /dev/null +++ b/.github/workflows/memory-testing.yml @@ -0,0 +1,43 @@ +--- +name: Test for memory leaks + +on: + pull_request: + paths: + - "crates/bitwarden-crypto/**" + - "crates/memory-testing/**" + push: + paths: + - "crates/bitwarden-crypto/**" + - "crates/memory-testing/**" + branches: + - "main" + - "rc" + - "hotfix-rc" + +jobs: + memory-test: + name: Testing + runs-on: ubuntu-22.04 + + steps: + - name: Check out repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Set up gdb + run: | + sudo apt update + sudo apt -y install gdb + + - name: Install rust + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable + with: + toolchain: stable + + - name: Cache cargo registry + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + key: memtest-cargo + + - name: Test + run: ./crates/memory-testing/run_test.sh no-docker diff --git a/.github/workflows/minimum-rust-version.yml b/.github/workflows/minimum-rust-version.yml new file mode 100644 index 000000000..e371d4026 --- /dev/null +++ b/.github/workflows/minimum-rust-version.yml @@ -0,0 +1,46 @@ +--- +name: Minimum Rust Version + +on: + pull_request: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + msrv: + name: Check MSRV for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} + runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} + strategy: + fail-fast: false + matrix: + settings: + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Install rust + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # 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 + targets: ${{ matrix.settings.target }} + + - name: Cache cargo registry + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + with: + key: msrv-${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} + + - name: cargo check MSRV + run: cargo check -p bitwarden --all-features diff --git a/.github/workflows/publish-dotnet.yml b/.github/workflows/publish-dotnet.yml index 7f438b24c..73f930c1a 100644 --- a/.github/workflows/publish-dotnet.yml +++ b/.github/workflows/publish-dotnet.yml @@ -1,69 +1,78 @@ -name: Deploy NuGet Package +name: Publish .NET NuGet +run-name: Publish .NET NuGet Package ${{ inputs.release_type }} on: workflow_dispatch: - version_number: - description: "New Version" - required: true + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run -jobs: - generate_schemas: - uses: ./.github/workflows/generate_schemas.yml - - build_rust: - uses: ./.github/workflows/build-rust-cross-platform.yml +env: + _KEY_VAULT: "bitwarden-ci" - deploy: - name: Deploy +jobs: + validate: + name: Setup runs-on: ubuntu-22.04 - needs: - - generate_schemas - - build_rust - + outputs: + version: ${{ steps.version.outputs.version }} steps: - - name: Checkout Repository - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Download C# schemas artifact - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - with: - name: schemas.cs - path: languages/csharp/Bitwarden.Sdk + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "===================================" + exit 1 + fi - - name: Set up .NET Core - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - with: - global-json-file: languages/csharp/global.json + - name: Install xmllint + run: sudo apt-get install -y libxml2-utils - - name: Download x86_64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - with: - name: libbitwarden_c_files-x86_64-apple-darwin - path: languages/csharp/Bitwarden.Sdk/macos-x64 + - 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 - - name: Download aarch64-apple-darwin files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + deploy: + name: Deploy + runs-on: ubuntu-22.04 + needs: validate + steps: + - name: Download NuGet package + uses: bitwarden/gh-actions/download-artifacts@main with: - name: libbitwarden_c_files-aarch64-apple-darwin - path: languages/csharp/Bitwarden.Sdk/macos-arm64 + 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 - - name: Download x86_64-unknown-linux-gnu files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - name: Login to Azure - Prod Subscription + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: - name: libbitwarden_c_files-x86_64-unknown-linux-gnu - path: languages/csharp/Bitwarden.Sdk/ubuntu-x64 + creds: ${{ secrets.AZURE_CI_SERVICE_PRINCIPAL }} - - name: Download x86_64-pc-windows-msvc files - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + - name: Retrieve secrets + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: - name: libbitwarden_c_files-x86_64-pc-windows-msvc - path: languages/csharp/Bitwarden.Sdk/windows-x64 - - - name: Pack NuGet Package - env: - VERSION: ${{ github.event.inputs.version_number }} - run: dotnet pack --configuration Release -p:PackageID=Bitwarden.Sdk -p:Version=${VERSION} --output ./nuget-output /nologo /v:n - working-directory: languages/csharp/Bitwarden.Sdk + keyvault: ${{ env._KEY_VAULT }} + secrets: "nuget-api-key" - name: Publish NuGet Package - run: dotnet nuget push ./languages/csharp/Bitwarden.Sdk/nuget-output/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json + if: ${{ inputs.release_type != 'Dry Run' }} + 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 diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml new file mode 100644 index 000000000..dca601213 --- /dev/null +++ b/.github/workflows/publish-php.yml @@ -0,0 +1,269 @@ +name: Publish PHP SDK +run-name: Publish PHP SDK ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +env: + _KEY_VAULT: "bitwarden-ci" + +jobs: + validate: + name: Setup + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "===================================" + exit 1 + fi + + - name: Get version + id: version + run: | + VERSION=$(cat languages/php/composer.json | grep -Eo '"version": "[0-9]+\.[0-9]+\.[0-9]+"' | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + setup-php: + name: Setup PHP + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup PHP with PECL extension + uses: shivammathur/setup-php@6d7209f44a25a59e904b1ee9f3b0c33ab2cd888d # 2.29.0 + with: + php-version: "8.0" + tools: composer + extensions: ext-ffi + + - name: Composer check + run: | + composer update + composer install + composer validate + working-directory: languages/php/ + + repo-sync: + name: Push changed files to SDK PHP repo + runs-on: ubuntu-22.04 + needs: + - validate + - setup-php + env: + _BOT_EMAIL: 106330231+bitwarden-devops-bot@users.noreply.github.com + _BOT_NAME: bitwarden-devops-bot + _PKG_VERSION: ${{ needs.validate.outputs.version }} + steps: + - name: Checkout SDK repo + uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4.0.0 + with: + path: sdk + + - name: Login to Azure - Prod Subscription + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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: Checkout SDK-PHP repo + uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4.0.0 + with: + repository: bitwarden/sm-sdk-php + path: sm-sdk-php + ref: main + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + + - name: Setup Git + working-directory: sm-sdk-php + run: | + git config --local user.email "${{ env._BOT_EMAIL }}" + git config --local user.name "${{ env._BOT_NAME }}" + + - name: Update files + run: | + # Copy files to local sm-sdk-php repo path + cp --verbose -rf sdk/languages/php/. sm-sdk-php + + - name: Replace repo name + working-directory: sm-sdk-php + run: | + find . -name '*' -exec \ + sed -i -e 's/github.com\/bitwarden\/sdk\/languages\/php/github.com\/bitwarden\/sm-sdk-php/g' {} \; + + find . -name '*' -exec \ + sed -i -e 's/github.com\/bitwarden\/sdk/github.com\/bitwarden\/sm-sdk-php/g' {} \; + + - name: Push changes + working-directory: sm-sdk-php + run: | + git add . + git commit -m "Update Go SDK to ${{ github.sha }}" + + if [[ "${{ inputs.release_type }}" == "Dry Run" ]]; then + echo "===================================" + echo "[!] Dry Run - Skipping push" + echo "===================================" + git ls-files -m + exit 0 + else + git push origin main + fi + + - name: Create release tag on SDK Go repo + if: ${{ inputs.release_type != 'Dry Run' }} + working-directory: sm-sdk-php + run: | + # Check if tag exists, set output then exit 0 if true. + if git log v${{ env._PKG_VERSION }} >/dev/null 2>&1; then + echo "===================================" + echo "[!] Tag v${{ env._PKG_VERSION }} already exists" + echo "===================================" + exit 1 + fi + + git tag v${{ env._PKG_VERSION }} + git push origin v${{ env._PKG_VERSION }} + + github-release: + name: GitHub Release + runs-on: ubuntu-22.04 + needs: + - setup-php + - repo-sync + - validate + env: + _PKG_VERSION: ${{ needs.validate.outputs.version }} + steps: + - name: Login to Azure - Prod Subscription + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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 x86_64-apple-darwin artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + workflow_conclusion: success + branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-apple-darwin + skip_unpack: true + + - name: Download aarch64-apple-darwin artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + workflow_conclusion: success + branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-aarch64-apple-darwin + skip_unpack: true + + - 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: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-unknown-linux-gnu + skip_unpack: true + + - 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: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-pc-windows-msvc + skip_unpack: true + + - name: Rename build artifacts + run: | + mv libbitwarden_c_files-x86_64-apple-darwin.zip libbitwarden_c_files-x86_64-apple-darwin-$_PKG_VERSION.zip + mv libbitwarden_c_files-aarch64-apple-darwin.zip libbitwarden_c_files-aarch64-apple-darwin-$_PKG_VERSION.zip + mv libbitwarden_c_files-x86_64-unknown-linux-gnu.zip libbitwarden_c_files-x86_64-unknown-linux-gnu-$_PKG_VERSION.zip + mv libbitwarden_c_files-x86_64-pc-windows-msvc.zip libbitwarden_c_files-x86_64-pc-windows-msvc-$_PKG_VERSION.zip + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0 + with: + tag: v${{ env._PKG_VERSION }} + name: v${{ env._PKG_VERSION }} + body: "" + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + draft: true + repo: sm-sdk-php + owner: bitwarden + artifacts: "libbitwarden_c_files-x86_64-apple-darwin-${{ env._PKG_VERSION }}.zip, + libbitwarden_c_files-aarch64-apple-darwin-${{ env._PKG_VERSION }}.zip, + libbitwarden_c_files-x86_64-unknown-linux-gnu-${{ env._PKG_VERSION }}.zip, + libbitwarden_c_files-x86_64-pc-windows-msvc-${{ env._PKG_VERSION }}.zip" + + packagist-publish: + name: Publish to Packagist + runs-on: ubuntu-22.04 + needs: + - validate + - setup-php + - repo-sync + - github-release + steps: + - name: Login to Azure - Prod Subscription + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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, + packagist-key" + + - name: Checkout SDK-PHP repo + uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4.0.0 + with: + repository: bitwarden/sm-sdk-php + path: sm-sdk-php + ref: main + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + + - name: Publish version + if: ${{ inputs.release_type != 'Dry Run' }} + env: + PACKAGIST_KEY: ${{ steps.retrieve-secrets.outputs.packagist-key }} + run: curl -XPOST -H'content-type:application/json' 'https://packagist.org/api/update-package?username=bitwarden&apiToken=${{ env.PACKAGIST_KEY }}' -d'{"repository":{"url":"https://packagist.org/packages/bitwarden/sdk-secrets"}}' + working-directory: sm-sdk-php diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml new file mode 100644 index 000000000..780195161 --- /dev/null +++ b/.github/workflows/publish-python.yml @@ -0,0 +1,99 @@ +--- +name: Publish Python SDK +run-name: Publish Python SDK ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +defaults: + run: + shell: bash + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Branch check + if: ${{ github.event.inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "===================================" + exit 1 + fi + + publish: + name: Publish + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Install Python + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + with: + python-version: "3.9" + + - name: Install twine + run: pip install twine + + - name: Download artifacts + uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0 + 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: Move files + working-directory: ${{ github.workspace }}/target/wheels/dist + run: | + find . -maxdepth 2 -type f -print0 | xargs -0 mv -t . + rm -rf */ + + - name: Login to Azure + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + + - name: Retrieve pypi api token + id: retrieve-secret + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: "bitwarden-ci" + secrets: "pypi-api-token, + pypi-test-api-token" + + - name: Check + working-directory: ${{ github.workspace }}/target/wheels + run: twine check dist/* + + - name: Publish + if: ${{ github.event.inputs.release_type != 'Dry Run' }} + working-directory: ${{ github.workspace }}/target/wheels + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ steps.retrieve-secret.outputs.pypi-api-token }} + run: twine upload --repository pypi dist/* + + - name: Dry Run - Publish + if: ${{ github.event.inputs.release_type == 'Dry Run' }} + working-directory: ${{ github.workspace }}/target/wheels + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ steps.retrieve-secret.outputs.pypi-test-api-token }} + run: twine upload --repository testpypi dist/* diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml new file mode 100644 index 000000000..b97a7b730 --- /dev/null +++ b/.github/workflows/publish-ruby.yml @@ -0,0 +1,139 @@ +name: Publish Ruby SDK +run-name: Publish Ruby SDK ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +permissions: + contents: read + id-token: write + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Branch check + if: ${{ github.event.inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "===================================" + exit 1 + fi + + publish_ruby: + name: Publish Ruby + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Checkout Repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Set up Ruby + uses: ruby/setup-ruby@22fdc77bf4148f810455b226c90fb81b5cbc00a7 # v1.171.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 + 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 + + - 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.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 + + - 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: "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: Push gem to Rubygems + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials + gem push *.gem + env: + GEM_HOST_API_KEY: ${{ steps.retrieve-secrets.outputs.rubygem-api-key }} + working-directory: languages/ruby/bitwarden_sdk_secrets diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index 4cef3fe63..705624d7e 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -29,6 +29,26 @@ on: 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: @@ -43,7 +63,7 @@ jobs: packages_command: ${{ steps.packages-list.outputs.packages_command }} steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -61,6 +81,10 @@ jobs: 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 }} run: | if [[ "$PUBLISH_BITWARDEN" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_API" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_IDENTITY" == "false" ]]; then echo "===================================" @@ -87,6 +111,26 @@ jobs: 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-generators" + PACKAGES_LIST="$PACKAGES_LIST bitwarden-generators" + fi + echo "Packages command: " $PACKAGES_COMMAND echo "Packages list: " $PACKAGES_LIST @@ -100,34 +144,34 @@ jobs: - setup steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "cratesio-api-token" - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Install cargo-release run: cargo install cargo-release - name: Create GitHub deployment if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5 + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 5d4d3c3e7..a9bc9e3fe 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -8,17 +8,19 @@ on: release_type: description: "Release Options" required: true - default: "Initial Release" + default: "Release" type: choice options: - - Initial Release - - Redeploy + - Release - Dry Run defaults: run: shell: bash +env: + _AZ_REGISTRY: bitwardenprod.azurecr.io + jobs: setup: name: Setup @@ -27,7 +29,7 @@ jobs: release-version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -47,7 +49,7 @@ jobs: - name: Create GitHub deployment if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5 + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: token: "${{ secrets.GITHUB_TOKEN }}" @@ -58,7 +60,7 @@ jobs: - name: Download all Release artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-cli.yml path: packages @@ -67,22 +69,22 @@ jobs: - name: Dry Run - Download all artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-cli.yml path: packages workflow_conclusion: success - branch: master + branch: main - name: Get checksum files - uses: bitwarden/gh-actions/get-checksum@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + 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@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0 + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 env: PKG_VERSION: ${{ steps.version.outputs.version }} with: @@ -120,32 +122,32 @@ jobs: publish: name: Publish bws to crates.io - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: - setup steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "cratesio-api-token" - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Install cargo-release run: cargo install cargo-release @@ -156,3 +158,78 @@ jobs: PUBLISH_GRACE_SLEEP: 10 CARGO_REGISTRY_TOKEN: ${{ steps.retrieve-secrets.outputs.cratesio-api-token }} run: cargo-release release publish -p bws --execute --no-confirm + + publish-docker: + name: Publish docker versioned and latest image + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Generate tag list + id: tag-list + env: + VERSION: ${{ needs.setup.outputs.release-version }} + DRY_RUN: ${{ inputs.release_type == 'Dry Run' }} + run: | + if [[ "${DRY_RUN}" == "true" ]]; then + REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + IMAGE_TAG=$(echo "${REF}" | sed "s#/#-#g") # slash safe branch name + echo "tags=$_AZ_REGISTRY/bws:${IMAGE_TAG},bitwarden/bws:${IMAGE_TAG}" >> $GITHUB_OUTPUT + else + echo "tags=$_AZ_REGISTRY/bws:${VERSION},bitwarden/bws:${VERSION},$_AZ_REGISTRY/bws:latest,bitwarden/bws:latest" >> $GITHUB_OUTPUT + fi + + ########## Set up Docker ########## + - name: Set up QEMU emulators + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + ########## Login to Docker registries ########## + - name: Login to Azure - Prod Subscription + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 + with: + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + + - name: Login to Azure ACR + run: az acr login -n ${_AZ_REGISTRY%.azurecr.io} + + - 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: Setup Docker Trust + uses: bitwarden/gh-actions/setup-docker-trust@main + with: + azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} + azure-keyvault-name: "bitwarden-ci" + + - name: Build and push Docker image + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + with: + context: . + file: crates/bws/Dockerfile + platforms: | + linux/amd64, + linux/arm64/v8 + push: ${{ inputs.release_type != 'Dry Run' }} + tags: ${{ steps.tag-list.outputs.tags }} + secrets: | + "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' }} + run: | + docker logout + echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV diff --git a/.github/workflows/release-go.yml b/.github/workflows/release-go.yml new file mode 100644 index 000000000..8ea03e928 --- /dev/null +++ b/.github/workflows/release-go.yml @@ -0,0 +1,222 @@ +name: Release Go SDK +run-name: Release Go SDK ${{ inputs.release_type }} + +on: + workflow_dispatch: + inputs: + release_type: + description: "Release Options" + required: true + default: "Release" + type: choice + options: + - Release + - Dry Run + +env: + GO111MODULE: on + GO_VERSION: "^1.18" + _KEY_VAULT: "bitwarden-ci" + +jobs: + validate: + name: Setup + runs-on: ubuntu-22.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Branch check + if: ${{ inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "===================================" + exit 1 + fi + + - name: Get version + id: version + run: | + VERSION=$(cat languages/go/.version | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + repo-sync: + name: Push changed files to SDK Go 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 }} + steps: + - name: Checkout SDK repo + uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4.0.0 + with: + path: sdk + + - name: Login to Azure - Prod Subscription + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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: Checkout SDK-Go repo + uses: actions/checkout@1e31de5234b9f8995739874a8ce0492dc87873e2 # v4.0.0 + with: + repository: bitwarden/sm-sdk-go + path: sm-sdk-go + ref: main + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + + - name: Setup Git + working-directory: sm-sdk-go + run: | + git config --local user.email "${{ env._BOT_EMAIL }}" + git config --local user.name "${{ env._BOT_NAME }}" + + - name: Update files + run: | + # Copy files to local sm-sdk-go repo path + cp --verbose -rf sdk/languages/go/. sm-sdk-go + + - name: Download artifacts + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: generate_schemas.yml + path: sm-sdk-go + workflow_conclusion: success + branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: schemas.go + + - name: Replace repo name + working-directory: sm-sdk-go + run: | + find . -name '*' -exec \ + gsed -i -e 's/github.com\/bitwarden\/sdk\/languages\/go/github.com\/bitwarden\/sm-sdk-go/g' {} \; + + - name: Push changes + working-directory: sm-sdk-go + run: | + git add . + git commit -m "Update Go SDK to ${{ github.sha }}" + + if [[ "${{ inputs.release_type }}" == "Dry Run" ]]; then + echo "===================================" + echo "[!] Dry Run - Skipping push" + echo "===================================" + git ls-files -m + exit 0 + else + git push origin main + fi + + - name: Create release tag on SDK Go repo + if: ${{ inputs.release_type != 'Dry Run' }} + working-directory: sm-sdk-go + run: | + # Check if tag exists, set output then exit 0 if true. + if git log v${{ env._PKG_VERSION }} >/dev/null 2>&1; then + echo "===================================" + echo "[!] Tag v${{ env._PKG_VERSION }} already exists" + echo "===================================" + exit 1 + fi + + git tag v${{ env._PKG_VERSION }} + git push origin v${{ env._PKG_VERSION }} + + github-release: + name: GitHub Release + runs-on: ubuntu-22.04 + needs: + - repo-sync + - validate + env: + _PKG_VERSION: ${{ needs.validate.outputs.version }} + steps: + - name: Login to Azure - Prod Subscription + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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 x86_64-apple-darwin artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + workflow_conclusion: success + branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-apple-darwin + skip_unpack: true + + - name: Download aarch64-apple-darwin artifact + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-rust-cross-platform.yml + workflow_conclusion: success + branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-aarch64-apple-darwin + skip_unpack: true + + - 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: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-unknown-linux-gnu + skip_unpack: true + + - 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: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + artifacts: libbitwarden_c_files-x86_64-pc-windows-msvc + skip_unpack: true + + - name: Rename build artifacts + run: | + artifacts=("x86_64-apple-darwin" "aarch64-apple-darwin" "x86_64-unknown-linux-gnu" "x86_64-pc-windows-msvc") # aarch64-unknown-linux-gnu) + for value in "${artifacts[@]}" + do + unzip libbitwarden_c_files-$value.zip -d libbitwarden_c_files-$value + cd libbitwarden_c_files-$value + zip -Rj ../libbitwarden_c_files-$value-$_PKG_VERSION.zip 'libbitwarden_c.*' + cd .. + done + + ls ./libbitwarden_c_files-x86_64-apple-darwin-$_PKG_VERSION -lRa + + - name: Create release + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0 + with: + tag: v${{ env._PKG_VERSION }} + name: v${{ env._PKG_VERSION }} + body: "" + token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + draft: true + repo: sm-sdk-go + owner: bitwarden + artifacts: "libbitwarden_c_files-x86_64-apple-darwin-${{ env._PKG_VERSION }}.zip, + libbitwarden_c_files-aarch64-apple-darwin-${{ env._PKG_VERSION }}.zip, + libbitwarden_c_files-x86_64-unknown-linux-gnu-${{ env._PKG_VERSION }}.zip, + libbitwarden_c_files-x86_64-pc-windows-msvc-${{ env._PKG_VERSION }}.zip" diff --git a/.github/workflows/release-napi.yml b/.github/workflows/release-napi.yml index 5c4992fc5..de97249a3 100644 --- a/.github/workflows/release-napi.yml +++ b/.github/workflows/release-napi.yml @@ -33,7 +33,7 @@ jobs: release-version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} @@ -47,7 +47,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/release-version-check@main with: release-type: ${{ github.event.inputs.release_type }} project-type: ts @@ -56,7 +56,7 @@ jobs: - name: Create GitHub deployment if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5 + uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: token: "${{ secrets.GITHUB_TOKEN }}" @@ -90,10 +90,10 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Node - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: "npm" @@ -101,7 +101,7 @@ jobs: - name: Download schemas if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-napi.yml artifacts: schemas.ts @@ -111,13 +111,13 @@ jobs: - name: Dry Run - Download schemas if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + 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: master + branch: main - name: Install dependencies run: npm ci @@ -126,20 +126,20 @@ jobs: run: npm run tsc - name: Login to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + 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@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "npm-api-key" - name: Download artifacts if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-napi.yml path: ${{ github.workspace }}/crates/bitwarden-napi/artifacts @@ -148,12 +148,12 @@ jobs: - name: Dry Run - Download artifacts if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-napi.yml path: ${{ github.workspace }}/crates/bitwarden-napi/artifacts workflow_conclusion: success - branch: master + branch: main - name: Move artifacts run: npm run artifacts diff --git a/.github/workflows/release-wasm.yml b/.github/workflows/release-wasm.yml new file mode 100644 index 000000000..c4946a0e1 --- /dev/null +++ b/.github/workflows/release-wasm.yml @@ -0,0 +1,132 @@ +--- +name: Release @bitwarden/sdk-wasm +run-name: Release @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 + npm_publish: + description: "Publish to NPM registry" + required: true + default: true + type: boolean + +defaults: + run: + shell: bash + working-directory: languages/js/wasm + +jobs: + setup: + name: Setup + runs-on: ubuntu-22.04 + outputs: + release-version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Branch check + if: ${{ github.event.inputs.release_type != 'Dry Run' }} + run: | + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + echo "===================================" + echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "===================================" + exit 1 + fi + + - name: Check Release Version + id: version + uses: bitwarden/gh-actions/release-version-check@main + with: + release-type: ${{ github.event.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@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 + 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@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + state: "failure" + deployment-id: ${{ steps.deployment.outputs.deployment_id }} + + npm: + name: Publish NPM + 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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - 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" + + - name: Download artifacts + uses: bitwarden/gh-actions/download-artifacts@main + with: + workflow: build-wasm.yml + path: ${{ github.workspace }}/languages/js/wasm + workflow_conclusion: success + branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + + - 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: ${{ github.event.inputs.release_type != 'Dry Run' }} + run: npm publish --access public --registry=https://registry.npmjs.org/ --userconfig=./.npmrc diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index f77cb29f6..2ea84b6aa 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: branches: - - "master" + - "main" - "rc" - "hotfix-rc" pull_request: @@ -15,7 +15,7 @@ env: jobs: ci-pass: name: CI is green - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: - test - wasm @@ -25,47 +25,75 @@ jobs: test: name: ${{ matrix.os }} / ${{matrix.target || 'default' }} - runs-on: ${{ matrix.os || 'ubuntu-latest' }} + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} strategy: matrix: os: - - ubuntu-latest - - macOS-latest - - windows-latest + - ubuntu-22.04 + - macOS-12 + - windows-2022 steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Test run: cargo test --all-features + coverage: + name: Coverage + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Install rust + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable + with: + toolchain: stable + components: llvm-tools + + - name: Cache cargo registry + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 + + - name: Install cargo-llvm-cov + run: cargo install cargo-llvm-cov --version 0.5.38 + + - name: Generate coverage + run: cargo llvm-cov --all-features --lcov --output-path lcov.info --ignore-filename-regex "crates/bitwarden-api-" + + - name: Upload to codecov.io + uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + wasm: name: WASM - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable targets: wasm32-unknown-unknown - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Check run: cargo check -p bitwarden-wasm --target wasm32-unknown-unknown diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml new file mode 100644 index 000000000..dea39cfd6 --- /dev/null +++ b/.github/workflows/scan.yml @@ -0,0 +1,77 @@ +name: Scan + +on: + workflow_dispatch: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + pull_request_target: + types: [opened, synchronize] + +jobs: + check-run: + name: Check PR run + uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main + + sast: + name: SAST scan + runs-on: ubuntu-22.04 + needs: check-run + permissions: + contents: read + pull-requests: write + security-events: write + + steps: + - name: Check out repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Scan with Checkmarx + uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 #2.0.23 + env: + INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" + with: + project_name: ${{ github.repository }} + cx_tenant: ${{ secrets.CHECKMARX_TENANT }} + base_uri: https://ast.checkmarx.net/ + cx_client_id: ${{ secrets.CHECKMARX_CLIENT_ID }} + cx_client_secret: ${{ secrets.CHECKMARX_SECRET }} + additional_params: | + --report-format sarif \ + --filter "state=TO_VERIFY;PROPOSED_NOT_EXPLOITABLE;CONFIRMED;URGENT" \ + --output-path . ${{ env.INCREMENTAL }} + + - name: Upload Checkmarx results to GitHub + uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + with: + sarif_file: cx_result.sarif + + quality: + name: Quality scan + runs-on: ubuntu-22.04 + needs: check-run + permissions: + contents: read + pull-requests: write + + steps: + - name: Check out repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Scan with SonarCloud + uses: sonarsource/sonarcloud-github-action@49e6cd3b187936a73b8280d59ffd9da69df63ec9 # v2.1.1 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: > + -Dsonar.organization=${{ github.repository_owner }} + -Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }} + -Dsonar.exclusions=languages/** diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index c672edb61..b2809518b 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,5 +1,6 @@ --- name: Version Bump +run-name: Version Bump - v${{ inputs.version_number }} on: workflow_dispatch: @@ -9,53 +10,60 @@ on: required: true type: choice options: - - napi - bitwarden - - bitwarden-api-api - - bitwarden-api-identity - - cli - - bitwarden-json + - bws + - napi + - python-sdk + - ruby-sdk + - go-sdk + - dotnet-sdk + - php-sdk version_number: - description: "New Version" + description: "New version (example: '2024.1.0')" required: true - -defaults: - run: - shell: bash + cut_rc_branch: + description: "Cut RC branch?" + default: true + type: boolean jobs: bump_version: - name: "Bump ${{ github.event.inputs.project }} Version" + name: "Bump ${{ inputs.project }} Version to v${{ inputs.version_number }}" runs-on: ubuntu-22.04 steps: - - name: Checkout Branch - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@bb45937a053e097f8591208d8e74c90db1873d07 # stable with: toolchain: stable - name: Cache cargo registry - uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # v2.7.0 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Install cargo-release - run: cargo install cargo-edit + run: cargo install cargo-edit --locked - - name: Login to Azure - Prod Subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + - name: Login to Azure - CI Subscription + 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@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" - secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" + secrets: "github-gpg-private-key, + github-gpg-private-key-passphrase, + github-pat-bitwarden-devops-bot-repo-scope" + + - name: Checkout Branch + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: main + repository: bitwarden/sdk - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 + uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 with: gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }} passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }} @@ -65,69 +73,72 @@ jobs: - name: Create Version Branch id: branch env: - VERSION: ${{ github.event.inputs.version_number }} - PROJECT: ${{ github.event.inputs.project }} + VERSION: ${{ inputs.version_number }} + PROJECT: ${{ inputs.project }} run: git switch -c sdk-${PROJECT}_version_bump_${VERSION} + - name: Create Version Branch + id: create-branch + run: | + NAME=version_bump_${{ github.ref_name }}_${{ inputs.project }}_${{ inputs.version_number }} + git switch -c $NAME + echo "name=$NAME" >> $GITHUB_OUTPUT + ######################## # VERSION BUMP SECTION # ######################## ### napi - name: Bump @bitwarden/sdk-napi Version - if: ${{ github.event.inputs.project == 'napi' }} - env: - VERSION: ${{ github.event.inputs.version_number }} + if: ${{ inputs.project == 'napi' }} working-directory: "crates/bitwarden-napi" run: | npm ci - npm version ${VERSION} + npm version ${{ inputs.version_number }} - name: Bump napi crate Version - if: ${{ github.event.inputs.project == 'napi' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-napi ${VERSION} + if: ${{ inputs.project == 'napi' }} + run: cargo set-version -p bitwarden-napi ${{ inputs.version_number }} ### bitwarden - name: Bump bitwarden crate Version - if: ${{ github.event.inputs.project == 'bitwarden' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden ${VERSION} - - ### bitwarden-api-api + if: ${{ inputs.project == 'bitwarden' }} + run: cargo set-version -p bitwarden ${{ inputs.version_number }} - - name: Bump bitwarden-api-api crate Version - if: ${{ github.event.inputs.project == 'bitwarden-api-api' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-api-api ${VERSION} - - ### bitwarden-api-identity + ### bws - - name: Bump bitwarden-api-identity crate Version - if: ${{ github.event.inputs.project == 'bitwarden-api-identity' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-api-identity ${VERSION} + - name: Bump bws Version + if: ${{ inputs.project == 'bws' }} + run: cargo set-version -p bws ${{ inputs.version_number }} - ### cli - - - name: Bump cli Version - if: ${{ github.event.inputs.project == 'cli' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bws ${VERSION} - - ### bitwarden-json - - - name: Bump bitwarden-json crate Version - if: ${{ github.event.inputs.project == 'bitwarden-json' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - run: cargo-set-version set-version -p bitwarden-json ${VERSION} + ### python + - name: Bump python-sdk Version + if: ${{ inputs.project == 'python-sdk' }} + run: | + sed -i 's/version = "[0-9]\.[0-9]\.[0-9]"/version = "${{ inputs.version_number }}"/' ./languages/python/pyproject.toml + sed -i 's/__version__ = "[0-9]\.[0-9]\.[0-9]"/__version__ = "${{ inputs.version_number }}"/' ./languages/python/bitwarden_sdk/__init__.py + + ### ruby sdk + - name: Bump ruby-sdk Version + if: ${{ inputs.project == 'ruby-sdk' }} + run: sed -i "s/VERSION = '[0-9]\.[0-9]\.[0-9]'/VERSION = '${{ inputs.version_number }}'/" ./languages/ruby/bitwarden_sdk_secrets/lib/version.rb + + ### go sdk + - name: Bump go-sdk Version + if: ${{ inputs.project == 'go-sdk' }} + run: sed -i 's/[0-9]\.[0-9]\.[0-9]/${{ inputs.version_number }}/' ./languages/go/.version + + ### dotnet sdk + - name: Bump dotnet-sdk Version + if: ${{ inputs.project == 'dotnet-sdk' }} + run: sed -i 's/[0-9]\.[0-9]\.[0-9]<\/Version>/${{ inputs.version_number }}<\/Version>/' languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj + + ### php sdk + - name: Bump php-sdk Version + if: ${{ inputs.project == 'php-sdk' }} + run: | + sed -i 's/"version": "[0-9]\.[0-9]\.[0-9]"/"version": "${{ inputs.version_number }}"/' ./languages/php/composer.json ############################ # VERSION BUMP SECTION END # @@ -156,28 +167,24 @@ jobs: - name: Commit files if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} - env: - VERSION: ${{ github.event.inputs.version_number }} - PROJECT: ${{ github.event.inputs.project }} - run: git commit -m "Bumped sdk-${PROJECT} version to ${VERSION}" -a + run: git commit -m "Bumped sdk-${{ inputs.project }} version to ${{ inputs.version_number }}" -a - name: Push changes if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} env: - VERSION: ${{ github.event.inputs.version_number }} - PROJECT: ${{ github.event.inputs.project }} - run: git push -u origin sdk-${PROJECT}_version_bump_${VERSION} + PR_BRANCH: ${{ steps.create-branch.outputs.name }} + run: git push -u origin $PR_BRANCH - - name: Create Bump Version PR + - name: Create Version PR if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} + id: create-pr env: - PR_BRANCH: "sdk-${{ github.event.inputs.project }}_version_bump_${{ github.event.inputs.version_number }}" - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - BASE_BRANCH: master - TITLE: "Bump ${{ github.event.inputs.project }} version to ${{ github.event.inputs.version_number }}" + GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + PR_BRANCH: ${{ steps.create-branch.outputs.name }} + TITLE: "Bump version to ${{ inputs.version_number }}" run: | - gh pr create --title "$TITLE" \ - --base "$BASE" \ + PR_URL=$(gh pr create --title "$TITLE" \ + --base "main" \ --head "$PR_BRANCH" \ --label "version update" \ --label "automated pr" \ @@ -190,4 +197,42 @@ jobs: - [X] Other ## Objective - Automated ${{ github.event.inputs.project }} version bump to ${{ github.event.inputs.version_number }}" + Automated ${{ inputs.project }} version bump to ${{ inputs.version_number }}") + echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT + + - name: Approve PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }} + run: gh pr review $PR_NUMBER --approve + + - name: Merge PR + env: + GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} + PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }} + run: gh pr merge $PR_NUMBER --squash --auto --delete-branch + + cut_rc: + name: Cut RC branch + needs: bump_version + if: ${{ inputs.cut_rc_branch == true }} + runs-on: ubuntu-22.04 + steps: + - name: Checkout Branch + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: main + + - name: Check if RC branch exists + run: | + remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l) + if [[ "${remote_rc_branch_check}" -gt 0 ]]; then + echo "Remote RC branch exists." + echo "Please delete current RC branch before running again." + exit 1 + fi + + - name: Cut RC branch + run: | + git switch --quiet --create rc + git push --quiet --set-upstream origin rc diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml new file mode 100644 index 000000000..24f10f1e4 --- /dev/null +++ b/.github/workflows/workflow-linter.yml @@ -0,0 +1,12 @@ +--- +name: Workflow linter + +on: + pull_request: + paths: + - .github/workflows/** + +jobs: + call-workflow: + name: Lint + uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main diff --git a/.gitignore b/.gitignore index 3f459493b..b13651d19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target .DS_Store .pytest_cache +.vscode/c_cpp_properties.json # Build results [Dd]ebug/ @@ -11,8 +12,15 @@ x64/ x86/ build/ bld/ -[Bb]in/ [Oo]bj/ +*.wasm + +# Binary files +*.dylib +*.a +*.so +*.dll +*.class # Editor directories and files .idea @@ -34,18 +42,21 @@ crates/bitwarden-napi/sdk-napi.*.node # Complied TypeScript client crates/bitwarden-napi/dist +languages/js/sdk-client/dist/ # Uniffi languages/swift/BitwardenFFI.xcframework languages/swift/tmp languages/swift/.build languages/swift/.swiftpm -languages/kotlin/sdk/src/main/java/com/bitwarden/sdk/bitwarden_uniffi.kt -languages/kotlin/sdk/src/main/java/com/bitwarden/core/bitwarden.kt +languages/kotlin/sdk/src/main/java/com/bitwarden/**/*.kt # Schemas -support/schemas crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts +languages/cpp/include/schemas.hpp languages/csharp/Bitwarden.Sdk/schemas.cs -languages/js_webassembly/bitwarden_client/schemas.ts -languages/python/BitwardenClient/schemas.py +languages/go/schema.go +languages/java/src/main/java/com/bitwarden/sdk/schema +languages/js/sdk-client/src/schemas.ts +languages/python/bitwarden_sdk/schemas.py +support/schemas diff --git a/.prettierignore b/.prettierignore index d5ffe5a0e..36c418776 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,9 +1,9 @@ target languages/* -!/languages/kotlin -languages/kotlin/* -!/languages/kotlin/doc.md schemas /crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts about.hbs support/docs/template.hbs + +# Test fixtures +crates/bitwarden-exporters/resources/* diff --git a/.vscode/settings.json b/.vscode/settings.json index e75498e9c..e92fcfb76 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,8 +15,12 @@ "Pbkdf", "PKCS8", "repr", + "reprompt", + "reqwest", "schemars", + "totp", "uniffi", - "wordlist" + "wordlist", + "zxcvbn" ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 55dc10eb2..71400c06e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,7 +6,7 @@ "command": "build", "problemMatcher": ["$rustc"], "options": { - "cwd": "${workspaceFolder}/bitwarden-c/" + "cwd": "${workspaceFolder}/crates/bitwarden-c/" }, "group": { "kind": "build", @@ -19,7 +19,7 @@ "command": "build", "args": ["--release"], "options": { - "cwd": "${workspaceFolder}/bitwarden-c/" + "cwd": "${workspaceFolder}/crates/bitwarden-c/" }, "problemMatcher": ["$rustc"], "label": "rust: bitwarden-c release build" @@ -46,6 +46,17 @@ "options": { "cwd": "${workspaceFolder}/languages/python" } + }, + { + "label": "buildJava", + "type": "shell", + "command": "gradle", + "args": ["build"], + "dependsOrder": "sequence", + "dependsOn": ["rust: bitwarden-c build"], + "options": { + "cwd": "${workspaceFolder}/languages/java" + } } ] } diff --git a/Cargo.lock b/Cargo.lock index 58fb5ff26..bda3a9c4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,20 +19,21 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", + "zeroize", ] [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -63,9 +64,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -77,60 +78,61 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arc-swap" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" [[package]] name = "argon2" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", + "zeroize", ] [[package]] @@ -145,9 +147,9 @@ dependencies = [ [[package]] name = "askama_derive" -version = "0.12.2" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a0fc7dcf8bd4ead96b1d36b41df47c14beedf7b0301fc543d8f2384e66a2ec0" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" dependencies = [ "askama_parser", "basic-toml", @@ -156,7 +158,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -167,9 +169,9 @@ checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" [[package]] name = "askama_parser" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c268a96e01a4c47c8c5c2472aaa570707e006a875ea63e819f75474ceedaf7b4" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" dependencies = [ "nom", ] @@ -185,40 +187,38 @@ dependencies = [ ] [[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - -[[package]] -name = "async-channel" -version = "1.9.0" +name = "async-compat" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b" dependencies = [ - "concurrent-queue", - "event-listener", "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", ] [[package]] name = "async-lock" -version = "2.8.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ "event-listener", + "event-listener-strategy", + "pin-project-lite", ] [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -244,15 +244,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -262,18 +256,18 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-toml" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" dependencies = [ "serde", ] [[package]] name = "bat" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4b13b0233143ae151a66e0135d715b65f631d1028c40502cc88182bcb9f4fa" +checksum = "9dcc9e5637c2330d8eb7b920f2aa5d9e184446c258466f825ea1412c7614cc86" dependencies = [ "ansi_colours", "bincode", @@ -281,17 +275,17 @@ dependencies = [ "clircle", "console", "content_inspector", - "dirs", - "encoding", + "encoding_rs", "flate2", "globset", + "home", "nu-ansi-term", "once_cell", "path_abs", "plist", "semver", "serde", - "serde_yaml 0.8.26", + "serde_yaml", "syntect", "thiserror", "unicode-width", @@ -306,6 +300,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -314,37 +323,32 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitwarden" -version = "0.3.0" +version = "0.4.0" dependencies = [ - "aes", - "argon2", - "assert_matches", - "base64 0.21.4", + "base64", "bitwarden-api-api", "bitwarden-api-identity", - "cbc", + "bitwarden-crypto", + "bitwarden-exporters", + "bitwarden-generators", "chrono", - "getrandom 0.2.10", - "hkdf", + "getrandom", "hmac", - "lazy_static", "log", - "num-bigint", - "num-traits", - "pbkdf2", - "rand 0.8.5", + "rand", + "rand_chacha", "reqwest", - "rsa", + "rustls-platform-verifier", "schemars", "serde", "serde_json", - "serde_qs 0.12.0", + "serde_qs", "serde_repr", "sha1", "sha2", @@ -353,11 +357,13 @@ dependencies = [ "uniffi", "uuid", "wiremock", + "zeroize", + "zxcvbn", ] [[package]] name = "bitwarden-api-api" -version = "0.2.1" +version = "0.4.0" dependencies = [ "reqwest", "serde", @@ -370,7 +376,7 @@ dependencies = [ [[package]] name = "bitwarden-api-identity" -version = "0.2.1" +version = "0.4.0" dependencies = [ "reqwest", "serde", @@ -392,7 +398,7 @@ dependencies = [ [[package]] name = "bitwarden-cli" -version = "0.1.0" +version = "0.4.0" dependencies = [ "clap", "color-eyre", @@ -400,10 +406,72 @@ dependencies = [ "supports-color", ] +[[package]] +name = "bitwarden-crypto" +version = "0.4.0" +dependencies = [ + "aes", + "argon2", + "base64", + "cbc", + "generic-array", + "hkdf", + "hmac", + "num-bigint", + "num-traits", + "pbkdf2", + "rand", + "rand_chacha", + "rayon", + "rsa", + "schemars", + "serde", + "serde_json", + "sha1", + "sha2", + "subtle", + "thiserror", + "uniffi", + "uuid", + "zeroize", +] + +[[package]] +name = "bitwarden-exporters" +version = "0.4.0" +dependencies = [ + "base64", + "bitwarden-crypto", + "chrono", + "csv", + "serde", + "serde_json", + "thiserror", + "uuid", +] + +[[package]] +name = "bitwarden-generators" +version = "0.4.0" +dependencies = [ + "bitwarden-crypto", + "rand", + "rand_chacha", + "reqwest", + "schemars", + "serde", + "serde_json", + "thiserror", + "tokio", + "uniffi", + "wiremock", +] + [[package]] name = "bitwarden-json" version = "0.3.0" dependencies = [ + "async-lock", "bitwarden", "log", "schemars", @@ -413,7 +481,7 @@ dependencies = [ [[package]] name = "bitwarden-napi" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bitwarden-json", "env_logger", @@ -441,16 +509,20 @@ version = "0.1.0" dependencies = [ "async-lock", "bitwarden", + "bitwarden-crypto", + "bitwarden-generators", + "chrono", "env_logger", - "openssl", "schemars", "uniffi", + "uuid", ] [[package]] name = "bitwarden-wasm" version = "0.1.0" dependencies = [ + "argon2", "bitwarden-json", "console_error_panic_hook", "console_log", @@ -491,9 +563,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -501,9 +573,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bw" @@ -522,10 +594,11 @@ dependencies = [ [[package]] name = "bws" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bat", "bitwarden", + "bitwarden-cli", "chrono", "clap", "clap_complete", @@ -534,23 +607,23 @@ dependencies = [ "directories", "env_logger", "log", - "openssl", + "regex", "serde", "serde_json", - "serde_yaml 0.9.25", + "serde_yaml", "supports-color", "tempfile", "thiserror", "tokio", - "toml 0.8.2", + "toml 0.8.12", "uuid", ] [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -581,9 +654,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" dependencies = [ "serde", ] @@ -613,12 +686,15 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" @@ -628,15 +704,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-targets 0.48.5", + "wasm-bindgen", + "windows-targets 0.52.4", ] [[package]] @@ -647,13 +725,14 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] name = "clap" -version = "4.4.6" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" dependencies = [ "clap_builder", "clap_derive", @@ -661,48 +740,48 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.0", ] [[package]] name = "clap_complete" -version = "4.4.3" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ae8ba90b9d8b007efe66e55e48fb936272f5ca00349b5b0e89877520d35ea7" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clircle" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68bbd985a63de680ab4d1ad77b6306611a8f961b282c8b5ab513e6de934e396" +checksum = "c8e87cbed5354f17bd8ca8821a097fb62599787fe8f611743fad7ee156a0a600" dependencies = [ "cfg-if", "libc", @@ -712,9 +791,9 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -727,9 +806,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", "owo-colors", @@ -743,13 +822,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "comfy-table" -version = "7.0.1" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" dependencies = [ - "crossterm 0.26.1", + "crossterm 0.27.0", "strum", "strum_macros", "unicode-width", @@ -757,24 +846,24 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -800,9 +889,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "content_inspector" @@ -824,9 +913,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -834,37 +923,53 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] -name = "crossbeam-utils" -version = "0.8.16" +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crossterm" version = "0.25.0" @@ -883,17 +988,14 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "crossterm_winapi", "libc", - "mio", "parking_lot", - "signal-hook", - "signal-hook-mio", "winapi", ] @@ -916,26 +1018,81 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctor" -version = "0.2.5" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c" +dependencies = [ + "quote", + "syn 2.0.53", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", "quote", - "syn 2.0.38", + "syn 1.0.109", ] [[package]] name = "deadpool" -version = "0.9.5" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490" dependencies = [ "async-trait", "deadpool-runtime", "num_cpus", - "retain_mut", "tokio", ] @@ -958,9 +1115,43 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] [[package]] name = "digest" @@ -983,15 +1174,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs-sys" version = "0.4.1" @@ -1006,15 +1188,15 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "encode_unicode" @@ -1023,89 +1205,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "encoding" -version = "0.2.33" +name = "encoding_rs" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" -dependencies = [ - "encoding-index-japanese", - "encoding-index-korean", - "encoding-index-simpchinese", - "encoding-index-singlebyte", - "encoding-index-tradchinese", -] - -[[package]] -name = "encoding-index-japanese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-korean" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-simpchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-singlebyte" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-tradchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ - "encoding_index_tests", + "cfg-if", ] [[package]] -name = "encoding_index_tests" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" - -[[package]] -name = "encoding_rs" -version = "0.8.33" +name = "env_filter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ - "cfg-if", + "log", + "regex", ] [[package]] name = "env_logger" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -1116,48 +1244,53 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "event-listener" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ - "cc", - "libc", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "event-listener" -version = "2.5.3" +name = "event-listener-strategy" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener", + "pin-project-lite", +] [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] -name = "fastrand" -version = "1.9.0" +name = "fancy-regex" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ - "instant", + "bit-set", + "regex", ] [[package]] @@ -1168,9 +1301,9 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -1182,41 +1315,29 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1229,9 +1350,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1239,15 +1360,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1256,59 +1377,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1330,37 +1430,27 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -1370,22 +1460,22 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] name = "goblin" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6b4de4a8eb6c46a8c77e1d3be942cb9a8bf073c22374578e5ba4b08ed0ff68" +checksum = "bb07a4ffed2093b118a525b1d8f5204ae274faed5604537caf7135d0f18d9887" dependencies = [ "log", "plain", @@ -1394,9 +1484,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", @@ -1404,7 +1494,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.2.5", "slab", "tokio", "tokio-util", @@ -1419,9 +1509,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -1429,17 +1519,29 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1453,11 +1555,20 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" -version = "0.2.9" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1466,34 +1577,25 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", "http", - "pin-project-lite", ] [[package]] -name = "http-types" -version = "2.12.0" +name = "http-body-util" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ - "anyhow", - "async-channel", - "base64 0.13.1", - "futures-lite", + "bytes", + "futures-core", "http", - "infer", + "http-body", "pin-project-lite", - "rand 0.7.3", - "serde", - "serde_json", - "serde_qs 0.8.5", - "serde_urlencoded", - "url", ] [[package]] @@ -1516,13 +1618,12 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", "http", @@ -1531,38 +1632,60 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", "hyper", - "native-tls", + "pin-project-lite", + "socket2", "tokio", - "tokio-native-tls", + "tower", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -1574,11 +1697,17 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1603,25 +1732,19 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.3", ] [[package]] name = "indoc" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" - -[[package]] -name = "infer" -version = "0.2.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "inout" @@ -1650,57 +1773,66 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "ipnet" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "ipnet" -version = "2.8.0" +name = "is_ci" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] -name = "is-terminal" -version = "0.4.9" +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", + "either", ] -[[package]] -name = "is_ci" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" - [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1711,23 +1843,23 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] name = "libc" -version = "0.2.148" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "winapi", + "windows-targets 0.52.4", ] [[package]] @@ -1736,6 +1868,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.5.0", + "libc", + "redox_syscall", +] + [[package]] name = "line-wrap" version = "0.1.1" @@ -1745,23 +1888,17 @@ dependencies = [ "safemem", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1769,15 +1906,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memoffset" @@ -1788,6 +1925,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memory-testing" +version = "0.1.0" +dependencies = [ + "bitwarden-crypto", + "comfy-table", + "hex", + "serde", + "serde_json", + "zeroize", +] + [[package]] name = "mime" version = "0.3.17" @@ -1812,32 +1961,32 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] [[package]] name = "napi" -version = "2.13.3" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd063c93b900149304e3ba96ce5bf210cd4f81ef5eb80ded0d100df3e85a3ac0" +checksum = "54a63d0570e4c3e0daf7a8d380563610e159f538e20448d6c911337246f40e84" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "ctor", "napi-derive", "napi-sys", @@ -1847,29 +1996,29 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" +checksum = "2f9130fccc5f763cf2069b34a089a18f0d0883c66aceb81f2fad541a3d823c43" [[package]] name = "napi-derive" -version = "2.13.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" +checksum = "05bb7c37e3c1dda9312fdbe4a9fc7507fca72288ba154ec093e2d49114e727ce" dependencies = [ "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.53", ] [[package]] name = "napi-derive-backend" -version = "1.0.52" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" +checksum = "f785a8b8d7b83e925f5aa6d2ae3c159d17fe137ac368dc185bef410e7acdaeb4" dependencies = [ "convert_case", "once_cell", @@ -1877,36 +2026,18 @@ dependencies = [ "quote", "regex", "semver", - "syn 1.0.109", + "syn 2.0.53", ] [[package]] name = "napi-sys" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" +checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b" dependencies = [ "libloading", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "newline-converter" version = "0.2.2" @@ -1928,11 +2059,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.47.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df031e117bca634c262e9bd3173776844b6c17a90b3741c9163663b4385af76" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" dependencies = [ - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1958,26 +2089,31 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand", "smallvec", "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -1986,9 +2122,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -2006,18 +2142,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oneshot-uniffi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c" [[package]] name = "onig" @@ -2041,60 +2183,12 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "openssl" -version = "0.10.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" -dependencies = [ - "bitflags 2.4.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "300.1.5+3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -2109,9 +2203,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -2125,13 +2219,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", "windows-targets 0.48.5", ] @@ -2143,7 +2237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2182,9 +2276,29 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +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.53", +] [[package]] name = "pin-project-lite" @@ -2221,9 +2335,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plain" @@ -2233,18 +2347,30 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plist" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" +checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" dependencies = [ - "base64 0.21.4", - "indexmap 1.9.3", + "base64", + "indexmap 2.2.5", "line-wrap", "quick-xml", "serde", "time", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2253,24 +2379,25 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.68" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "parking_lot", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -2279,9 +2406,9 @@ dependencies = [ [[package]] name = "pyo3-asyncio" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2cc34c1f907ca090d7add03dc523acdd91f3a4dab12286604951e2f5152edad" +checksum = "6ea6b68e93db3622f3bb3bf363246cf948ed5375afe7abff98ccbdd50b184995" dependencies = [ "futures", "once_cell", @@ -2293,9 +2420,9 @@ dependencies = [ [[package]] name = "pyo3-asyncio-macros" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4045f06429547179e4596f5c0b13c82efc8b04296016780133653ed69ce26b3" +checksum = "56c467178e1da6252c95c29ecf898b133f742e9181dca5def15dc24e19d45a39" dependencies = [ "proc-macro2", "quote", @@ -2304,9 +2431,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" dependencies = [ "once_cell", "target-lexicon", @@ -2314,9 +2441,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" dependencies = [ "libc", "pyo3-build-config", @@ -2324,9 +2451,9 @@ dependencies = [ [[package]] name = "pyo3-log" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47b0777feb17f61eea78667d61103758b243a871edc09a7786500a50467b605" +checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd" dependencies = [ "arc-swap", "log", @@ -2335,58 +2462,53 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 1.0.109", + "syn 2.0.53", ] [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" dependencies = [ + "heck 0.4.1", "proc-macro2", + "pyo3-build-config", "quote", - "syn 1.0.109", + "syn 2.0.53", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -2394,18 +2516,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -2415,16 +2527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -2433,52 +2536,54 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rayon" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ - "rand_core 0.5.1", + "either", + "rayon-core", ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "bitflags 1.3.2", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", + "getrandom", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.9.6" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -2488,9 +2593,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2499,103 +2604,204 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "58b48d98d932f4ee75e541614d32a7f44c889b72bd9c2e04d95edd135989df88" dependencies = [ - "base64 0.21.4", + "base64", "bytes", - "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", + "http-body-util", "hyper", - "hyper-tls", + "hyper-rustls", + "hyper-util", "ipnet", "js-sys", "log", "mime", "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile 1.0.4", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "system-configuration", + "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] [[package]] -name = "retain_mut" -version = "0.1.9" +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.1", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] [[package]] -name = "rgb" -version = "0.8.36" +name = "rustls-pemfile" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" dependencies = [ - "bytemuck", + "base64", + "rustls-pki-types", ] [[package]] -name = "rsa" -version = "0.9.2" +name = "rustls-pki-types" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" + +[[package]] +name = "rustls-platform-verifier" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "2c35b9a497e588f1fb2e1d18a0d46a6d057710f34c3da7084b27353b319453cc" dependencies = [ - "byteorder", - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-iter", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-roots", + "winapi", ] [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "rustls-platform-verifier-android" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" [[package]] -name = "rustix" -version = "0.38.17" +name = "rustls-webpki" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -2606,9 +2812,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "safemem" @@ -2627,18 +2833,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "chrono", "dyn-clone", @@ -2651,9 +2857,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -2675,22 +2881,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scroll" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -2701,7 +2907,7 @@ dependencies = [ "bitwarden", "bitwarden-json", "bitwarden-uniffi", - "itertools", + "itertools 0.12.1", "schemars", "serde_json", ] @@ -2716,6 +2922,7 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] @@ -2731,31 +2938,31 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.188" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] @@ -2771,26 +2978,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", "serde", ] -[[package]] -name = "serde_qs" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" -dependencies = [ - "percent-encoding", - "serde", - "thiserror", -] - [[package]] name = "serde_qs" version = "0.12.0" @@ -2804,20 +3000,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -2836,23 +3032,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" -dependencies = [ - "indexmap 1.9.3", - "ryu", - "serde", - "yaml-rust", -] - -[[package]] -name = "serde_yaml" -version = "0.9.25" +version = "0.9.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.2.5", "itoa", "ryu", "serde", @@ -2922,12 +3106,12 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2947,28 +3131,24 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "socket2" -version = "0.4.9" +name = "smawk" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2977,11 +3157,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -3005,23 +3191,29 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.53", ] [[package]] @@ -3032,11 +3224,10 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "supports-color" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" +checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" dependencies = [ - "is-terminal", "is_ci", ] @@ -3053,20 +3244,26 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "syntect" -version = "5.1.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" dependencies = [ "bincode", "bitflags 1.3.2", @@ -3076,85 +3273,66 @@ dependencies = [ "onig", "regex-syntax", "serde", + "serde_derive", "serde_json", "thiserror", "walkdir", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "target-lexicon" -version = "0.12.11" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "tempfile" -version = "3.8.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.1", - "redox_syscall 0.3.5", + "fastrand", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "termcolor" -version = "1.3.0" +name = "textwrap" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ - "winapi-util", + "smawk", + "unicode-linebreak", + "unicode-width", ] [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -3162,12 +3340,14 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -3181,10 +3361,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -3205,9 +3386,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -3215,37 +3396,38 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.4", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "native-tls", + "rustls", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -3266,9 +3448,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -3278,26 +3460,48 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.2.5", "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" @@ -3306,20 +3510,20 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", + "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -3337,9 +3541,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "sharded-slab", "thread_local", @@ -3348,9 +3552,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -3369,9 +3573,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3379,20 +3583,26 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" @@ -3402,8 +3612,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "uniffi" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad0be8bba6c242d2d16922de4a9c8f167b9491729fda552e70f8626bf7302cb" dependencies = [ "anyhow", "camino", @@ -3423,8 +3634,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab31006ab9c9c6870739f0e74235729d1478d82e73571b8f53c25aa176d67535" dependencies = [ "anyhow", "askama", @@ -3434,11 +3646,11 @@ dependencies = [ "fs-err", "glob", "goblin", - "heck", + "heck 0.4.1", "once_cell", "paste", "serde", - "serde_json", + "textwrap", "toml 0.5.11", "uniffi_meta", "uniffi_testing", @@ -3447,8 +3659,9 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa3a7608c6872dc1ce53199d816a24d2e19af952d82ce557ecc8692a4ae9cba" dependencies = [ "anyhow", "camino", @@ -3457,32 +3670,36 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72775b3afa6adb30e0c92b3107858d2fcb0ff1a417ac242db1f648b0e2dd0ef2" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.53", ] [[package]] name = "uniffi_core" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6e8db3f4e558faf0e25ac4b5bd775567973a4e18809f1123e74de52a853692" dependencies = [ "anyhow", + "async-compat", "bytes", "camino", - "cargo_metadata", "log", "once_cell", + "oneshot-uniffi", "paste", "static_assertions", ] [[package]] name = "uniffi_macros" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a126650799f97d97d8e38e3f10f15c65f5bc5a76b021bec21823efe9dd831a02" dependencies = [ "bincode", "camino", @@ -3491,7 +3708,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.38", + "syn 2.0.53", "toml 0.5.11", "uniffi_build", "uniffi_meta", @@ -3499,36 +3716,37 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f64a99e905671738d9d293f9cce58708ce1af8e13ea29f9d6b6925114fc2e85" dependencies = [ "anyhow", "bytes", - "serde", "siphasher", "uniffi_checksum_derive", ] [[package]] name = "uniffi_testing" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdca5719a22edf34c8239cc6ac9e3906d7ebc2a3e8a5e6ece4c3dffc312a4251" dependencies = [ "anyhow", "camino", "cargo_metadata", "fs-err", "once_cell", - "serde", - "serde_json", ] [[package]] name = "uniffi_udl" -version = "0.24.1" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6817c15714acccd0d0459f99b524cabebfdd622376464a2c6466a6485bdb4b" dependencies = [ "anyhow", + "textwrap", "uniffi_meta", "uniffi_testing", "weedle2", @@ -3536,26 +3754,31 @@ dependencies = [ [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] @@ -3566,10 +3789,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.4.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ + "getrandom", "serde", ] @@ -3579,29 +3803,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" - [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -3616,12 +3828,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3630,9 +3836,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "serde", @@ -3642,24 +3848,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -3669,9 +3875,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3679,28 +3885,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-bindgen-test" -version = "0.3.37" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" dependencies = [ "console_error_panic_hook", "js-sys", @@ -3712,28 +3918,39 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.37" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", + "syn 2.0.53", ] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weedle2" -version = "4.0.0" -source = "git+https://github.com/mozilla/uniffi-rs?rev=53d5ac7274d8b4d66ad35b68cb6e2d89898f96af#53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" dependencies = [ "nom", ] @@ -3770,21 +3987,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -3797,18 +4005,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -3827,10 +4029,19 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "windows-targets" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] [[package]] name = "windows_aarch64_gnullvm" @@ -3839,10 +4050,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +name = "windows_aarch64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -3851,10 +4062,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "windows_i686_gnu" -version = "0.42.2" +name = "windows_aarch64_msvc" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -3863,10 +4074,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnu" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -3875,10 +4086,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +name = "windows_i686_msvc" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -3887,10 +4098,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +name = "windows_x86_64_gnu" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -3899,10 +4110,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "windows_x86_64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -3910,11 +4121,17 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "winnow" -version = "0.5.16" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] @@ -3931,37 +4148,60 @@ dependencies = [ [[package]] name = "wiremock" -version = "0.5.19" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f71803d3a1c80377a06221e0530be02035d5b3e854af56c6ece7ac20ac441d" +checksum = "ec874e1eef0df2dcac546057fe5e29186f09c378181cd7b635b4b7bcc98e9d81" dependencies = [ "assert-json-diff", "async-trait", - "base64 0.21.4", + "base64", "deadpool", "futures", - "futures-timer", - "http-types", + "http", + "http-body-util", "hyper", + "hyper-util", "log", "once_cell", "regex", "serde", "serde_json", "tokio", + "url", ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "zeroize" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ - "linked-hash-map", + "zeroize_derive", ] [[package]] -name = "zeroize" -version = "1.6.0" +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "zxcvbn" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "103fa851fff70ea29af380e87c25c48ff7faac5c530c70bd0e65366d4e0c94e4" +dependencies = [ + "derive_builder", + "fancy-regex", + "itertools 0.10.5", + "js-sys", + "lazy_static", + "quick-error", + "regex", + "time", +] diff --git a/Cargo.toml b/Cargo.toml index dbd4ed30f..75685a7bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,33 @@ [workspace] resolver = "2" - members = ["crates/*"] +# Global settings for all crates should be defined here +[workspace.package] +# Update using `cargo set-version -p bitwarden ` +version = "0.4.0" +authors = ["Bitwarden Inc"] +edition = "2021" +# Note: Changing rust-version should be considered a breaking change +rust-version = "1.71" +homepage = "https://bitwarden.com" +repository = "https://github.com/bitwarden/sdk" +license-file = "LICENSE" +keywords = ["bitwarden"] + +# Define dependencies that are expected to be consistent across all crates +[workspace.dependencies] +bitwarden = { path = "crates/bitwarden", version = "=0.4.0" } +bitwarden-api-api = { path = "crates/bitwarden-api-api", version = "=0.4.0" } +bitwarden-api-identity = { path = "crates/bitwarden-api-identity", version = "=0.4.0" } +bitwarden-cli = { path = "crates/bitwarden-cli", version = "=0.4.0" } +bitwarden-crypto = { path = "crates/bitwarden-crypto", version = "=0.4.0" } +bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=0.4.0" } +bitwarden-generators = { path = "crates/bitwarden-generators", version = "=0.4.0" } + +[workspace.lints.clippy] +unwrap_used = "deny" + # Compile all dependencies with some optimizations when building this crate on debug # This slows down clean builds by about 50%, but the resulting binaries can be orders of magnitude faster # As clean builds won't occur very often, this won't slow down the development process @@ -21,11 +46,3 @@ codegen-units = 1 # 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 - -# Uniffi proc-macro support is still not part of a release, so we need to use the git version for now -[patch.crates-io] -uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" } -uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" } -uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" } -uniffi_core = { git = "https://github.com/mozilla/uniffi-rs", rev = "53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" } -uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "53d5ac7274d8b4d66ad35b68cb6e2d89898f96af" } diff --git a/README.md b/README.md index 18613d4dc..a1ef88b7d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,16 @@ -# Bitwarden Secrets Manager SDK +# Bitwarden SDK -This repository houses the Bitwarden Secrets Manager SDK. The core SDK is written in Rust and -provides a Rust API, CLI and Node-API bindings. In the future more language bindings might be added. +This repository houses the Bitwarden SDKs. We currently provide a public Secrets Manager SDK and an +internal SDK for the Bitwarden Password Manager which is used for the native mobile applications. +The SDK is written in Rust and provides a Rust API, CLI and various language bindings. + +### Disclaimer + +The password manager SDK is not intended for public use and is not supported by Bitwarden at this +stage. It is solely intended to centralize the business logic and to provide a single source of +truth for the internal applications. As the SDK evolves into a more stable and feature complete +state we will re-evaluate the possibility of publishing stable bindings for the public. **The +password manager interface is unstable and will change without warning.** # We're Hiring! @@ -17,7 +26,8 @@ cargo build ## Crates -The project is structured as a monorepo using cargo workspaces. +The project is structured as a monorepo using cargo workspaces. Some of the more noteworthy crates +are: - [`bitwarden`](./crates/bitwarden/): Rust friendly API for interacting with the secrets manager. - [`bitwarden-api-api`](./crates/bitwarden-api-api/): Auto-generated API bindings for the API @@ -28,7 +38,8 @@ The project is structured as a monorepo using cargo workspaces. - [`bitwarden-json`](./crates/bitwarden-json/): JSON wrapper around the `bitwarden` crate. Powers the other language bindings. - [`bitwarden-napi`](./crates/bitwarden-napi/): Node-API bindings. -- [`bws`](./crates/bws/): CLI for interacting with the secrets manager. +- [`bws`](./crates/bws/): CLI for interacting with the [Bitwarden Secrets Manager][secrets-manager]. + Review the [CLI documentation][bws-help]. - [`sdk-schemas`](./crates/sdk-schemas/): Generator for the _json schemas_. ## Schemas @@ -57,43 +68,77 @@ The first step is to generate the swagger documents from the server repository. ```bash # src/Api -dotnet swagger tofile --output ../../api.json .\bin\Debug\net6.0\Api.dll internal +dotnet swagger tofile --output ../../api.json ./bin/Debug/net8.0/Api.dll internal # src/Identity -dotnet swagger tofile --output ../../identity.json .\bin\Debug\net6.0\Identity.dll v1 +ASPNETCORE_ENVIRONMENT=development dotnet swagger tofile --output ../../identity.json ./bin/Debug/net8.0/Identity.dll v1 ``` ### OpenApi Generator -Runs from the root of the SDK project. +To generate a new version of the bindings run the following script from the root of the SDK project. ```bash -npx openapi-generator-cli generate ` - -i ../server/api.json ` - -g rust ` - -o crates/bitwarden-api-api ` - --package-name bitwarden-api-api ` - -t ./support/openapi-template ` - --additional-properties=packageVersion=1.0.0 - -npx openapi-generator-cli generate ` - -i ../server/identity.json ` - -g rust ` - -o crates/bitwarden-api-identity ` - --package-name bitwarden-api-identity ` - -t ./support/openapi-template ` - --additional-properties=packageVersion=1.0.0 +./support/build-api.sh ``` -OpenApi Generator works using templates, we have customized our templates to work better with our -codebase. - -- https://github.com/OpenAPITools/openapi-generator/issues/10977 -- https://github.com/OpenAPITools/openapi-generator/issues/12464 - -There is also a scenario where we have a negative integer enum which completely breaks the openapi -generation. In that case we excluded the file from being generated and manually patched it. -`crates/bitwarden-api-api/src/models/organization_user_status_type.rs` +This project uses customized templates which lives in the `support/openapi-templates` directory. +These templates resolves some outstanding issues we've experienced with the rust generator. But we +strive towards modifying the templates as little as possible to ease future upgrades. + +Note: If you don't have the nightly toolchain installed, the `build-api.sh` script will install it +for you. + +## Tests + +Many of the SDK tests are based on encrypted data provided by the other Bitwarden clients. In order +to provide a consistent method of retrieving the data we provide a test account with user keys. + +**Disclaimer:** The server typically encrypts and protects certain fields. In order to allow +accounts to be used on other servers this protection was explicitly removed from these data dumps. + +### `test@bitwarden.com` + +- Email: `test@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 'b1fd4bf2-9643-4787-87f3-b0f00189c33b', + N 'Test', N 'test@bitwarden.com', + 0, N 'AQAAAAEAAYagAAAAEJ3ky9F/Zt5sy3/UAHVvBarMR+tBXYOM5IGgXy4/mx82uptgHgItauyCN+UZTvAqiA==', + null, N 'en-US', N 'F3KL7SCJKEXO4LJFVLGZITPEHM7SAVSZ', + null, null, null, null, N '2024-01-07 23:56:48.2600000', + N '2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=', + N 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Ww2chogqCpaAR7Uw448am4b7vDFXiM5kXjFlGfXBlrAdAqTTggEvTDlMNYqPlCo+mBM6iFmTTUY9rpZBvFskMnKvsvpJ47/fehAH2o2e3Ulv/5NFevaVCMCmpkBDtbMbO1A4a3btdRtCP8DsKWMefHauEpaoLxNTLWnOIZVfCMjsSgx2EvULHAZPTtbFwm4+UVKniM4ds4jvOsD85h4jn2aLs/jWJXFfxN8iVSqEqpC2TBvsPdyHb49xQoWWfF0Z6BiNqeNGKEU9Uos1pjL+kzhEzzSpH31PZT/ufJ/oo4+93wrUt57hb6f0jxiXhwd5yQ+9F6wVwpbfkq0IwhjOwIDAQAB', + N '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=', + 0, null, null, null, null, null, null, + null, N '2024-01-07 23:53:38.5900000', + N '2024-01-07 23:53:38.5900000', + null, 0, 600000, N '{"id":null}', N '7gp59kKHt9kMlks0BuNC4IjNXYkljR', + 0, 0, 0, null, null, null, null, null, + null, null, null + ); +``` -The hope going forward is that we can continue to use the generator with minimal manual -intervention. +[secrets-manager]: https://bitwarden.com/products/secrets-manager/ +[bws-help]: https://bitwarden.com/help/secrets-manager-cli/ diff --git a/about.toml b/about.toml index 2ed67c7cc..bd56148af 100644 --- a/about.toml +++ b/about.toml @@ -8,4 +8,9 @@ accepted = [ "MPL-2.0", "LGPL-3.0", "Unicode-DFS-2016", + "OpenSSL", ] + +# Ring has all the licenses combined into a single file, which causes cargo about to +# be confused about it. Thankfully it includes a workaround for this that we can enable. +workarounds = ["ring"] diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..a29e019ac --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +allow-unwrap-in-tests=true +allow-expect-in-tests=true diff --git a/crates/bitwarden-api-api/.openapi-generator-ignore b/crates/bitwarden-api-api/.openapi-generator-ignore index b72fc90d8..0a3fec13f 100644 --- a/crates/bitwarden-api-api/.openapi-generator-ignore +++ b/crates/bitwarden-api-api/.openapi-generator-ignore @@ -25,5 +25,3 @@ docs/*.md .travis.yml git_push.sh - -src/models/organization_user_status_type.rs diff --git a/crates/bitwarden-api-api/.openapi-generator/FILES b/crates/bitwarden-api-api/.openapi-generator/FILES index 4fbc2fbf9..206ad61b2 100644 --- a/crates/bitwarden-api-api/.openapi-generator/FILES +++ b/crates/bitwarden-api-api/.openapi-generator/FILES @@ -36,6 +36,7 @@ src/apis/provider_users_api.rs src/apis/providers_api.rs src/apis/push_api.rs src/apis/secrets_api.rs +src/apis/secrets_manager_events_api.rs src/apis/secrets_manager_porting_api.rs src/apis/self_hosted_organization_licenses_api.rs src/apis/self_hosted_organization_sponsorships_api.rs @@ -46,6 +47,7 @@ src/apis/sync_api.rs src/apis/trash_api.rs src/apis/two_factor_api.rs src/apis/users_api.rs +src/apis/web_authn_api.rs src/lib.rs src/models/access_policies_create_request.rs src/models/access_policy_request.rs @@ -55,20 +57,30 @@ src/models/access_token_creation_response_model.rs src/models/access_token_response_model.rs src/models/access_token_response_model_list_response_model.rs src/models/admin_auth_request_update_request_model.rs +src/models/algorithm.rs src/models/api_key_response_model.rs +src/models/assertion_options.rs +src/models/assertion_response.rs src/models/attachment_request_model.rs src/models/attachment_response_model.rs src/models/attachment_upload_data_response_model.rs +src/models/attestation_conveyance_preference.rs src/models/auth_request_create_request_model.rs src/models/auth_request_response_model.rs src/models/auth_request_response_model_list_response_model.rs src/models/auth_request_type.rs src/models/auth_request_update_request_model.rs +src/models/authentication_extensions_client_inputs.rs src/models/authentication_extensions_client_outputs.rs +src/models/authenticator_assertion_raw_response.rs +src/models/authenticator_attachment.rs src/models/authenticator_attestation_raw_response.rs +src/models/authenticator_selection.rs +src/models/authenticator_transport.rs src/models/base_access_policy_response_model.rs src/models/base_secret_response_model.rs src/models/base_secret_response_model_list_response_model.rs +src/models/billing_customer_discount.rs src/models/billing_history_response_model.rs src/models/billing_invoice.rs src/models/billing_payment_response_model.rs @@ -79,7 +91,7 @@ src/models/billing_subscription_item.rs src/models/billing_subscription_upcoming_invoice.rs src/models/billing_transaction.rs src/models/bit_pay_invoice_request_model.rs -src/models/bitwarden_product_type.rs +src/models/bulk_collection_access_request_model.rs src/models/bulk_delete_response_model.rs src/models/bulk_delete_response_model_list_response_model.rs src/models/bulk_deny_admin_auth_request_request_model.rs @@ -93,6 +105,7 @@ src/models/cipher_collections_request_model.rs src/models/cipher_create_request_model.rs src/models/cipher_details_response_model.rs src/models/cipher_details_response_model_list_response_model.rs +src/models/cipher_fido2_credential_model.rs src/models/cipher_field_model.rs src/models/cipher_identity_model.rs src/models/cipher_login_model.rs @@ -120,8 +133,10 @@ src/models/collection_response_model.rs src/models/collection_response_model_list_response_model.rs src/models/collection_with_id_request_model.rs src/models/config_response_model.rs +src/models/credential_create_options.rs src/models/delete_recover_request_model.rs src/models/device_keys_request_model.rs +src/models/device_keys_update_request_model.rs src/models/device_request_model.rs src/models/device_response_model.rs src/models/device_response_model_list_response_model.rs @@ -143,11 +158,13 @@ src/models/emergency_access_takeover_response_model.rs src/models/emergency_access_type.rs src/models/emergency_access_update_request_model.rs src/models/emergency_access_view_response_model.rs +src/models/emergency_access_with_id_request_model.rs src/models/environment_config_response_model.rs src/models/event_response_model.rs src/models/event_response_model_list_response_model.rs src/models/event_system_user.rs src/models/event_type.rs +src/models/fido2_user.rs src/models/field_type.rs src/models/file_upload_type.rs src/models/folder_request_model.rs @@ -166,7 +183,6 @@ 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/iap_check_request_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 @@ -192,6 +208,7 @@ 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_auto_enroll_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 @@ -201,11 +218,12 @@ src/models/organization_domain_response_model.rs src/models/organization_domain_response_model_list_response_model.rs src/models/organization_domain_sso_details_request_model.rs src/models/organization_domain_sso_details_response_model.rs -src/models/organization_enroll_secrets_manager_request_model.rs src/models/organization_keys_request_model.rs src/models/organization_keys_response_model.rs src/models/organization_license.rs +src/models/organization_public_key_response_model.rs src/models/organization_response_model.rs +src/models/organization_risks_subscription_failure_response_model.rs src/models/organization_seat_request_model.rs src/models/organization_sponsorship_create_request_model.rs src/models/organization_sponsorship_redeem_request_model.rs @@ -235,19 +253,23 @@ 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_enrollment_request_model.rs src/models/organization_user_reset_password_request_model.rs +src/models/organization_user_status_type.rs src/models/organization_user_type.rs src/models/organization_user_update_groups_request_model.rs src/models/organization_user_update_request_model.rs src/models/organization_user_user_details_response_model.rs src/models/organization_user_user_details_response_model_list_response_model.rs src/models/organization_verify_bank_request_model.rs +src/models/other_device_keys_update_request_model.rs src/models/password_hint_request_model.rs +src/models/password_manager_plan_features_response_model.rs src/models/password_request_model.rs src/models/payment_method_type.rs src/models/payment_request_model.rs src/models/payment_response_model.rs src/models/pending_organization_auth_request_response_model.rs src/models/pending_organization_auth_request_response_model_list_response_model.rs +src/models/people_access_policies_request_model.rs src/models/permissions.rs src/models/plan_response_model.rs src/models/plan_response_model_list_response_model.rs @@ -269,9 +291,11 @@ src/models/profile_provider_response_model.rs src/models/profile_response_model.rs src/models/project_access_policies_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 src/models/project_response_model_list_response_model.rs src/models/project_update_request_model.rs +src/models/protected_device_response_model.rs src/models/provider_organization_add_request_model.rs src/models/provider_organization_create_request_model.rs src/models/provider_organization_organization_details_response_model.rs @@ -297,6 +321,9 @@ src/models/provider_user_type.rs src/models/provider_user_update_request_model.rs src/models/provider_user_user_details_response_model.rs src/models/provider_user_user_details_response_model_list_response_model.rs +src/models/pub_key_cred_param.rs +src/models/public_key_credential_descriptor.rs +src/models/public_key_credential_rp_entity.rs src/models/public_key_credential_type.rs src/models/push_registration_request_model.rs src/models/push_send_request_model.rs @@ -304,6 +331,7 @@ 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/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 @@ -316,6 +344,8 @@ src/models/secret_update_request_model.rs src/models/secret_verification_request_model.rs src/models/secret_with_projects_inner_project.rs src/models/secret_with_projects_list_response_model.rs +src/models/secrets_manager_plan_features_response_model.rs +src/models/secrets_manager_subscribe_request_model.rs src/models/secrets_manager_subscription_update_request_model.rs src/models/secrets_with_projects_inner_secret.rs src/models/secure_note_type.rs @@ -332,12 +362,13 @@ 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_policies_response_model.rs src/models/service_account_create_request_model.rs +src/models/service_account_people_access_policies_response_model.rs src/models/service_account_project_access_policy_response_model.rs src/models/service_account_project_access_policy_response_model_list_response_model.rs src/models/service_account_response_model.rs -src/models/service_account_response_model_list_response_model.rs +src/models/service_account_secrets_details_response_model.rs +src/models/service_account_secrets_details_response_model_list_response_model.rs src/models/service_account_update_request_model.rs src/models/set_key_connector_key_request_model.rs src/models/set_password_request_model.rs @@ -370,6 +401,7 @@ src/models/two_factor_web_authn_request_model.rs src/models/two_factor_web_authn_response_model.rs src/models/two_factor_yubi_key_response_model.rs src/models/update_avatar_request_model.rs +src/models/update_devices_trust_request_model.rs src/models/update_domains_request_model.rs src/models/update_key_request_model.rs src/models/update_profile_request_model.rs @@ -384,6 +416,14 @@ 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 src/models/verify_otp_request_model.rs +src/models/web_authn_credential_create_options_response_model.rs +src/models/web_authn_credential_response_model.rs +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_prf_status.rs diff --git a/crates/bitwarden-api-api/.openapi-generator/VERSION b/crates/bitwarden-api-api/.openapi-generator/VERSION index 4be2c727a..4b49d9bb6 100644 --- a/crates/bitwarden-api-api/.openapi-generator/VERSION +++ b/crates/bitwarden-api-api/.openapi-generator/VERSION @@ -1 +1 @@ -6.5.0 \ No newline at end of file +7.2.0 \ No newline at end of file diff --git a/crates/bitwarden-api-api/Cargo.toml b/crates/bitwarden-api-api/Cargo.toml index fd5780dad..cfe357998 100644 --- a/crates/bitwarden-api-api/Cargo.toml +++ b/crates/bitwarden-api-api/Cargo.toml @@ -1,26 +1,29 @@ [package] name = "bitwarden-api-api" -version = "0.2.1" -authors = ["Bitwarden Inc"] -license-file = "LICENSE" -repository = "https://github.com/bitwarden/sdk" -homepage = "https://bitwarden.com" description = """ Api bindings for the Bitwarden API. """ -keywords = ["bitwarden"] categories = ["api-bindings"] -edition = "2018" + +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] -serde = "^1.0.163" -serde_derive = "^1.0.163" -serde_json = "^1.0.96" -serde_repr = "^0.1.12" -url = "^2.3.1" -uuid = { version = "^1.3.3", features = ["serde"] } +serde = ">=1.0.163, <2" +serde_derive = ">=1.0.163, <2" +serde_json = ">=1.0.96, <2" +serde_repr = ">=0.1.12, <0.2" +url = ">=2.3.1, <3" +uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] -version = "^0.11.18" -features = ["json", "multipart"] +version = ">=0.12, <0.13" +features = ["http2", "json", "multipart"] +default-features = false [dev-dependencies] diff --git a/crates/bitwarden-api-api/LICENSE b/crates/bitwarden-api-api/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden-api-api/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden-api-api/README.md b/crates/bitwarden-api-api/README.md index 255041dc5..b4dac8d9f 100644 --- a/crates/bitwarden-api-api/README.md +++ b/crates/bitwarden-api-api/README.md @@ -26,17 +26,19 @@ 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_ | [**access_policies_id_delete**](docs/AccessPoliciesApi.md#access_policies_id_delete) | **DELETE** /access-policies/{id} | | _AccessPoliciesApi_ | [**access_policies_id_put**](docs/AccessPoliciesApi.md#access_policies_id_put) | **PUT** /access-policies/{id} | | _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 | | _AccessPoliciesApi_ | [**projects_id_access_policies_get**](docs/AccessPoliciesApi.md#projects_id_access_policies_get) | **GET** /projects/{id}/access-policies | +| _AccessPoliciesApi_ | [**projects_id_access_policies_people_get**](docs/AccessPoliciesApi.md#projects_id_access_policies_people_get) | **GET** /projects/{id}/access-policies/people | +| _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_post**](docs/AccessPoliciesApi.md#projects_id_access_policies_post) | **POST** /projects/{id}/access-policies | -| _AccessPoliciesApi_ | [**service_accounts_id_access_policies_get**](docs/AccessPoliciesApi.md#service_accounts_id_access_policies_get) | **GET** /service-accounts/{id}/access-policies | -| _AccessPoliciesApi_ | [**service_accounts_id_access_policies_post**](docs/AccessPoliciesApi.md#service_accounts_id_access_policies_post) | **POST** /service-accounts/{id}/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 | | _AccessPoliciesApi_ | [**service_accounts_id_granted_policies_post**](docs/AccessPoliciesApi.md#service_accounts_id_granted_policies_post) | **POST** /service-accounts/{id}/granted-policies | | _AccountsApi_ | [**accounts_api_key_post**](docs/AccountsApi.md#accounts_api_key_post) | **POST** /accounts/api-key | @@ -50,7 +52,6 @@ All URIs are relative to _http://localhost_ | _AccountsApi_ | [**accounts_delete_recover_token_post**](docs/AccountsApi.md#accounts_delete_recover_token_post) | **POST** /accounts/delete-recover-token | | _AccountsApi_ | [**accounts_email_post**](docs/AccountsApi.md#accounts_email_post) | **POST** /accounts/email | | _AccountsApi_ | [**accounts_email_token_post**](docs/AccountsApi.md#accounts_email_token_post) | **POST** /accounts/email-token | -| _AccountsApi_ | [**accounts_iap_check_post**](docs/AccountsApi.md#accounts_iap_check_post) | **POST** /accounts/iap-check | | _AccountsApi_ | [**accounts_kdf_post**](docs/AccountsApi.md#accounts_kdf_post) | **POST** /accounts/kdf | | _AccountsApi_ | [**accounts_key_post**](docs/AccountsApi.md#accounts_key_post) | **POST** /accounts/key | | _AccountsApi_ | [**accounts_keys_get**](docs/AccountsApi.md#accounts_keys_get) | **GET** /accounts/keys | @@ -86,6 +87,7 @@ All URIs are relative to _http://localhost_ | _AccountsApi_ | [**accounts_verify_password_post**](docs/AccountsApi.md#accounts_verify_password_post) | **POST** /accounts/verify-password | | _AccountsBillingApi_ | [**accounts_billing_history_get**](docs/AccountsBillingApi.md#accounts_billing_history_get) | **GET** /accounts/billing/history | | _AccountsBillingApi_ | [**accounts_billing_payment_method_get**](docs/AccountsBillingApi.md#accounts_billing_payment_method_get) | **GET** /accounts/billing/payment-method | +| _AuthRequestsApi_ | [**auth_requests_admin_request_post**](docs/AuthRequestsApi.md#auth_requests_admin_request_post) | **POST** /auth-requests/admin-request | | _AuthRequestsApi_ | [**auth_requests_get**](docs/AuthRequestsApi.md#auth_requests_get) | **GET** /auth-requests | | _AuthRequestsApi_ | [**auth_requests_id_get**](docs/AuthRequestsApi.md#auth_requests_id_get) | **GET** /auth-requests/{id} | | _AuthRequestsApi_ | [**auth_requests_id_put**](docs/AuthRequestsApi.md#auth_requests_id_put) | **PUT** /auth-requests/{id} | @@ -146,6 +148,7 @@ All URIs are relative to _http://localhost_ | _CiphersApi_ | [**ciphers_share_post**](docs/CiphersApi.md#ciphers_share_post) | **POST** /ciphers/share | | _CiphersApi_ | [**ciphers_share_put**](docs/CiphersApi.md#ciphers_share_put) | **PUT** /ciphers/share | | _CollectionsApi_ | [**collections_get**](docs/CollectionsApi.md#collections_get) | **GET** /collections | +| _CollectionsApi_ | [**organizations_org_id_collections_bulk_access_post**](docs/CollectionsApi.md#organizations_org_id_collections_bulk_access_post) | **POST** /organizations/{orgId}/collections/bulk-access | | _CollectionsApi_ | [**organizations_org_id_collections_delete**](docs/CollectionsApi.md#organizations_org_id_collections_delete) | **DELETE** /organizations/{orgId}/collections | | _CollectionsApi_ | [**organizations_org_id_collections_delete_post**](docs/CollectionsApi.md#organizations_org_id_collections_delete_post) | **POST** /organizations/{orgId}/collections/delete | | _CollectionsApi_ | [**organizations_org_id_collections_details_get**](docs/CollectionsApi.md#organizations_org_id_collections_details_get) | **GET** /organizations/{orgId}/collections/details | @@ -162,7 +165,6 @@ All URIs are relative to _http://localhost_ | _CollectionsApi_ | [**organizations_org_id_collections_id_users_put**](docs/CollectionsApi.md#organizations_org_id_collections_id_users_put) | **PUT** /organizations/{orgId}/collections/{id}/users | | _CollectionsApi_ | [**organizations_org_id_collections_post**](docs/CollectionsApi.md#organizations_org_id_collections_post) | **POST** /organizations/{orgId}/collections | | _ConfigApi_ | [**config_get**](docs/ConfigApi.md#config_get) | **GET** /config | -| _DevicesApi_ | [**devices_exist_by_types_post**](docs/DevicesApi.md#devices_exist_by_types_post) | **POST** /devices/exist-by-types | | _DevicesApi_ | [**devices_get**](docs/DevicesApi.md#devices_get) | **GET** /devices | | _DevicesApi_ | [**devices_id_delete**](docs/DevicesApi.md#devices_id_delete) | **DELETE** /devices/{id} | | _DevicesApi_ | [**devices_id_delete_post**](docs/DevicesApi.md#devices_id_delete_post) | **POST** /devices/{id}/delete | @@ -176,9 +178,11 @@ All URIs are relative to _http://localhost_ | _DevicesApi_ | [**devices_identifier_identifier_token_put**](docs/DevicesApi.md#devices_identifier_identifier_token_put) | **PUT** /devices/identifier/{identifier}/token | | _DevicesApi_ | [**devices_identifier_keys_post**](docs/DevicesApi.md#devices_identifier_keys_post) | **POST** /devices/{identifier}/keys | | _DevicesApi_ | [**devices_identifier_keys_put**](docs/DevicesApi.md#devices_identifier_keys_put) | **PUT** /devices/{identifier}/keys | +| _DevicesApi_ | [**devices_identifier_retrieve_keys_post**](docs/DevicesApi.md#devices_identifier_retrieve_keys_post) | **POST** /devices/{identifier}/retrieve-keys | | _DevicesApi_ | [**devices_knowndevice_email_identifier_get**](docs/DevicesApi.md#devices_knowndevice_email_identifier_get) | **GET** /devices/knowndevice/{email}/{identifier} | | _DevicesApi_ | [**devices_knowndevice_get**](docs/DevicesApi.md#devices_knowndevice_get) | **GET** /devices/knowndevice | | _DevicesApi_ | [**devices_post**](docs/DevicesApi.md#devices_post) | **POST** /devices | +| _DevicesApi_ | [**devices_update_trust_post**](docs/DevicesApi.md#devices_update_trust_post) | **POST** /devices/update-trust | | _EmergencyAccessApi_ | [**emergency_access_granted_get**](docs/EmergencyAccessApi.md#emergency_access_granted_get) | **GET** /emergency-access/granted | | _EmergencyAccessApi_ | [**emergency_access_id_accept_post**](docs/EmergencyAccessApi.md#emergency_access_id_accept_post) | **POST** /emergency-access/{id}/accept | | _EmergencyAccessApi_ | [**emergency_access_id_approve_post**](docs/EmergencyAccessApi.md#emergency_access_id_approve_post) | **POST** /emergency-access/{id}/approve | @@ -234,7 +238,7 @@ 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 | @@ -302,9 +306,10 @@ All URIs are relative to _http://localhost_ | _OrganizationsApi_ | [**organizations_id_api_key_post**](docs/OrganizationsApi.md#organizations_id_api_key_post) | **POST** /organizations/{id}/api-key | | _OrganizationsApi_ | [**organizations_id_billing_get**](docs/OrganizationsApi.md#organizations_id_billing_get) | **GET** /organizations/{id}/billing | | _OrganizationsApi_ | [**organizations_id_cancel_post**](docs/OrganizationsApi.md#organizations_id_cancel_post) | **POST** /organizations/{id}/cancel | +| _OrganizationsApi_ | [**organizations_id_collection_management_put**](docs/OrganizationsApi.md#organizations_id_collection_management_put) | **PUT** /organizations/{id}/collection-management | | _OrganizationsApi_ | [**organizations_id_delete**](docs/OrganizationsApi.md#organizations_id_delete) | **DELETE** /organizations/{id} | | _OrganizationsApi_ | [**organizations_id_delete_post**](docs/OrganizationsApi.md#organizations_id_delete_post) | **POST** /organizations/{id}/delete | -| _OrganizationsApi_ | [**organizations_id_enroll_secrets_manager_post**](docs/OrganizationsApi.md#organizations_id_enroll_secrets_manager_post) | **POST** /organizations/{id}/enroll-secrets-manager | +| _OrganizationsApi_ | [**organizations_id_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 | @@ -313,14 +318,17 @@ All URIs are relative to _http://localhost_ | _OrganizationsApi_ | [**organizations_id_license_get**](docs/OrganizationsApi.md#organizations_id_license_get) | **GET** /organizations/{id}/license | | _OrganizationsApi_ | [**organizations_id_payment_post**](docs/OrganizationsApi.md#organizations_id_payment_post) | **POST** /organizations/{id}/payment | | _OrganizationsApi_ | [**organizations_id_post**](docs/OrganizationsApi.md#organizations_id_post) | **POST** /organizations/{id} | +| _OrganizationsApi_ | [**organizations_id_public_key_get**](docs/OrganizationsApi.md#organizations_id_public_key_get) | **GET** /organizations/{id}/public-key | | _OrganizationsApi_ | [**organizations_id_put**](docs/OrganizationsApi.md#organizations_id_put) | **PUT** /organizations/{id} | | _OrganizationsApi_ | [**organizations_id_reinstate_post**](docs/OrganizationsApi.md#organizations_id_reinstate_post) | **POST** /organizations/{id}/reinstate | +| _OrganizationsApi_ | [**organizations_id_risks_subscription_failure_get**](docs/OrganizationsApi.md#organizations_id_risks_subscription_failure_get) | **GET** /organizations/{id}/risks-subscription-failure | | _OrganizationsApi_ | [**organizations_id_rotate_api_key_post**](docs/OrganizationsApi.md#organizations_id_rotate_api_key_post) | **POST** /organizations/{id}/rotate-api-key | | _OrganizationsApi_ | [**organizations_id_seat_post**](docs/OrganizationsApi.md#organizations_id_seat_post) | **POST** /organizations/{id}/seat | | _OrganizationsApi_ | [**organizations_id_sm_subscription_post**](docs/OrganizationsApi.md#organizations_id_sm_subscription_post) | **POST** /organizations/{id}/sm-subscription | | _OrganizationsApi_ | [**organizations_id_sso_get**](docs/OrganizationsApi.md#organizations_id_sso_get) | **GET** /organizations/{id}/sso | | _OrganizationsApi_ | [**organizations_id_sso_post**](docs/OrganizationsApi.md#organizations_id_sso_post) | **POST** /organizations/{id}/sso | | _OrganizationsApi_ | [**organizations_id_storage_post**](docs/OrganizationsApi.md#organizations_id_storage_post) | **POST** /organizations/{id}/storage | +| _OrganizationsApi_ | [**organizations_id_subscribe_secrets_manager_post**](docs/OrganizationsApi.md#organizations_id_subscribe_secrets_manager_post) | **POST** /organizations/{id}/subscribe-secrets-manager | | _OrganizationsApi_ | [**organizations_id_subscription_get**](docs/OrganizationsApi.md#organizations_id_subscription_get) | **GET** /organizations/{id}/subscription | | _OrganizationsApi_ | [**organizations_id_subscription_post**](docs/OrganizationsApi.md#organizations_id_subscription_post) | **POST** /organizations/{id}/subscription | | _OrganizationsApi_ | [**organizations_id_tax_get**](docs/OrganizationsApi.md#organizations_id_tax_get) | **GET** /organizations/{id}/tax | @@ -329,12 +337,11 @@ All URIs are relative to _http://localhost_ | _OrganizationsApi_ | [**organizations_id_verify_bank_post**](docs/OrganizationsApi.md#organizations_id_verify_bank_post) | **POST** /organizations/{id}/verify-bank | | _OrganizationsApi_ | [**organizations_identifier_auto_enroll_status_get**](docs/OrganizationsApi.md#organizations_identifier_auto_enroll_status_get) | **GET** /organizations/{identifier}/auto-enroll-status | | _OrganizationsApi_ | [**organizations_post**](docs/OrganizationsApi.md#organizations_post) | **POST** /organizations | -| _PlansApi_ | [**plans_all_get**](docs/PlansApi.md#plans_all_get) | **GET** /plans/all | | _PlansApi_ | [**plans_get**](docs/PlansApi.md#plans_get) | **GET** /plans | | _PlansApi_ | [**plans_sales_tax_rates_get**](docs/PlansApi.md#plans_sales_tax_rates_get) | **GET** /plans/sales-tax-rates | -| _PlansApi_ | [**plans_sm_plans_get**](docs/PlansApi.md#plans_sm_plans_get) | **GET** /plans/sm-plans | | _PoliciesApi_ | [**organizations_org_id_policies_get**](docs/PoliciesApi.md#organizations_org_id_policies_get) | **GET** /organizations/{orgId}/policies | | _PoliciesApi_ | [**organizations_org_id_policies_invited_user_get**](docs/PoliciesApi.md#organizations_org_id_policies_invited_user_get) | **GET** /organizations/{orgId}/policies/invited-user | +| _PoliciesApi_ | [**organizations_org_id_policies_master_password_get**](docs/PoliciesApi.md#organizations_org_id_policies_master_password_get) | **GET** /organizations/{orgId}/policies/master-password | | _PoliciesApi_ | [**organizations_org_id_policies_token_get**](docs/PoliciesApi.md#organizations_org_id_policies_token_get) | **GET** /organizations/{orgId}/policies/token | | _PoliciesApi_ | [**organizations_org_id_policies_type_get**](docs/PoliciesApi.md#organizations_org_id_policies_type_get) | **GET** /organizations/{orgId}/policies/{type} | | _PoliciesApi_ | [**organizations_org_id_policies_type_put**](docs/PoliciesApi.md#organizations_org_id_policies_type_put) | **PUT** /organizations/{orgId}/policies/{type} | @@ -379,6 +386,7 @@ All URIs are relative to _http://localhost_ | _SecretsApi_ | [**secrets_get_by_ids_post**](docs/SecretsApi.md#secrets_get_by_ids_post) | **POST** /secrets/get-by-ids | | _SecretsApi_ | [**secrets_id_get**](docs/SecretsApi.md#secrets_id_get) | **GET** /secrets/{id} | | _SecretsApi_ | [**secrets_id_put**](docs/SecretsApi.md#secrets_id_put) | **PUT** /secrets/{id} | +| _SecretsManagerEventsApi_ | [**sm_events_service_accounts_service_account_id_get**](docs/SecretsManagerEventsApi.md#sm_events_service_accounts_service_account_id_get) | **GET** /sm/events/service-accounts/{serviceAccountId} | | _SecretsManagerPortingApi_ | [**sm_organization_id_export_get**](docs/SecretsManagerPortingApi.md#sm_organization_id_export_get) | **GET** /sm/{organizationId}/export | | _SecretsManagerPortingApi_ | [**sm_organization_id_import_post**](docs/SecretsManagerPortingApi.md#sm_organization_id_import_post) | **POST** /sm/{organizationId}/import | | _SelfHostedOrganizationLicensesApi_ | [**organizations_licenses_self_hosted_id_post**](docs/SelfHostedOrganizationLicensesApi.md#organizations_licenses_self_hosted_id_post) | **POST** /organizations/licenses/self-hosted/{id} | @@ -447,6 +455,12 @@ All URIs are relative to _http://localhost_ | _TwoFactorApi_ | [**two_factor_yubikey_post**](docs/TwoFactorApi.md#two_factor_yubikey_post) | **POST** /two-factor/yubikey | | _TwoFactorApi_ | [**two_factor_yubikey_put**](docs/TwoFactorApi.md#two_factor_yubikey_put) | **PUT** /two-factor/yubikey | | _UsersApi_ | [**users_id_public_key_get**](docs/UsersApi.md#users_id_public_key_get) | **GET** /users/{id}/public-key | +| _WebAuthnApi_ | [**webauthn_assertion_options_post**](docs/WebAuthnApi.md#webauthn_assertion_options_post) | **POST** /webauthn/assertion-options | +| _WebAuthnApi_ | [**webauthn_attestation_options_post**](docs/WebAuthnApi.md#webauthn_attestation_options_post) | **POST** /webauthn/attestation-options | +| _WebAuthnApi_ | [**webauthn_get**](docs/WebAuthnApi.md#webauthn_get) | **GET** /webauthn | +| _WebAuthnApi_ | [**webauthn_id_delete_post**](docs/WebAuthnApi.md#webauthn_id_delete_post) | **POST** /webauthn/{id}/delete | +| _WebAuthnApi_ | [**webauthn_post**](docs/WebAuthnApi.md#webauthn_post) | **POST** /webauthn | +| _WebAuthnApi_ | [**webauthn_put**](docs/WebAuthnApi.md#webauthn_put) | **PUT** /webauthn | ## Documentation For Models @@ -458,20 +472,30 @@ All URIs are relative to _http://localhost_ - [AccessTokenResponseModel](docs/AccessTokenResponseModel.md) - [AccessTokenResponseModelListResponseModel](docs/AccessTokenResponseModelListResponseModel.md) - [AdminAuthRequestUpdateRequestModel](docs/AdminAuthRequestUpdateRequestModel.md) +- [Algorithm](docs/Algorithm.md) - [ApiKeyResponseModel](docs/ApiKeyResponseModel.md) +- [AssertionOptions](docs/AssertionOptions.md) +- [AssertionResponse](docs/AssertionResponse.md) - [AttachmentRequestModel](docs/AttachmentRequestModel.md) - [AttachmentResponseModel](docs/AttachmentResponseModel.md) - [AttachmentUploadDataResponseModel](docs/AttachmentUploadDataResponseModel.md) +- [AttestationConveyancePreference](docs/AttestationConveyancePreference.md) - [AuthRequestCreateRequestModel](docs/AuthRequestCreateRequestModel.md) - [AuthRequestResponseModel](docs/AuthRequestResponseModel.md) - [AuthRequestResponseModelListResponseModel](docs/AuthRequestResponseModelListResponseModel.md) - [AuthRequestType](docs/AuthRequestType.md) - [AuthRequestUpdateRequestModel](docs/AuthRequestUpdateRequestModel.md) +- [AuthenticationExtensionsClientInputs](docs/AuthenticationExtensionsClientInputs.md) - [AuthenticationExtensionsClientOutputs](docs/AuthenticationExtensionsClientOutputs.md) +- [AuthenticatorAssertionRawResponse](docs/AuthenticatorAssertionRawResponse.md) +- [AuthenticatorAttachment](docs/AuthenticatorAttachment.md) - [AuthenticatorAttestationRawResponse](docs/AuthenticatorAttestationRawResponse.md) +- [AuthenticatorSelection](docs/AuthenticatorSelection.md) +- [AuthenticatorTransport](docs/AuthenticatorTransport.md) - [BaseAccessPolicyResponseModel](docs/BaseAccessPolicyResponseModel.md) - [BaseSecretResponseModel](docs/BaseSecretResponseModel.md) - [BaseSecretResponseModelListResponseModel](docs/BaseSecretResponseModelListResponseModel.md) +- [BillingCustomerDiscount](docs/BillingCustomerDiscount.md) - [BillingHistoryResponseModel](docs/BillingHistoryResponseModel.md) - [BillingInvoice](docs/BillingInvoice.md) - [BillingPaymentResponseModel](docs/BillingPaymentResponseModel.md) @@ -482,7 +506,7 @@ All URIs are relative to _http://localhost_ - [BillingSubscriptionUpcomingInvoice](docs/BillingSubscriptionUpcomingInvoice.md) - [BillingTransaction](docs/BillingTransaction.md) - [BitPayInvoiceRequestModel](docs/BitPayInvoiceRequestModel.md) -- [BitwardenProductType](docs/BitwardenProductType.md) +- [BulkCollectionAccessRequestModel](docs/BulkCollectionAccessRequestModel.md) - [BulkDeleteResponseModel](docs/BulkDeleteResponseModel.md) - [BulkDeleteResponseModelListResponseModel](docs/BulkDeleteResponseModelListResponseModel.md) - [BulkDenyAdminAuthRequestRequestModel](docs/BulkDenyAdminAuthRequestRequestModel.md) @@ -496,6 +520,7 @@ All URIs are relative to _http://localhost_ - [CipherCreateRequestModel](docs/CipherCreateRequestModel.md) - [CipherDetailsResponseModel](docs/CipherDetailsResponseModel.md) - [CipherDetailsResponseModelListResponseModel](docs/CipherDetailsResponseModelListResponseModel.md) +- [CipherFido2CredentialModel](docs/CipherFido2CredentialModel.md) - [CipherFieldModel](docs/CipherFieldModel.md) - [CipherIdentityModel](docs/CipherIdentityModel.md) - [CipherLoginModel](docs/CipherLoginModel.md) @@ -523,8 +548,10 @@ All URIs are relative to _http://localhost_ - [CollectionResponseModelListResponseModel](docs/CollectionResponseModelListResponseModel.md) - [CollectionWithIdRequestModel](docs/CollectionWithIdRequestModel.md) - [ConfigResponseModel](docs/ConfigResponseModel.md) +- [CredentialCreateOptions](docs/CredentialCreateOptions.md) - [DeleteRecoverRequestModel](docs/DeleteRecoverRequestModel.md) - [DeviceKeysRequestModel](docs/DeviceKeysRequestModel.md) +- [DeviceKeysUpdateRequestModel](docs/DeviceKeysUpdateRequestModel.md) - [DeviceRequestModel](docs/DeviceRequestModel.md) - [DeviceResponseModel](docs/DeviceResponseModel.md) - [DeviceResponseModelListResponseModel](docs/DeviceResponseModelListResponseModel.md) @@ -546,11 +573,13 @@ All URIs are relative to _http://localhost_ - [EmergencyAccessType](docs/EmergencyAccessType.md) - [EmergencyAccessUpdateRequestModel](docs/EmergencyAccessUpdateRequestModel.md) - [EmergencyAccessViewResponseModel](docs/EmergencyAccessViewResponseModel.md) +- [EmergencyAccessWithIdRequestModel](docs/EmergencyAccessWithIdRequestModel.md) - [EnvironmentConfigResponseModel](docs/EnvironmentConfigResponseModel.md) - [EventResponseModel](docs/EventResponseModel.md) - [EventResponseModelListResponseModel](docs/EventResponseModelListResponseModel.md) - [EventSystemUser](docs/EventSystemUser.md) - [EventType](docs/EventType.md) +- [Fido2User](docs/Fido2User.md) - [FieldType](docs/FieldType.md) - [FileUploadType](docs/FileUploadType.md) - [FolderRequestModel](docs/FolderRequestModel.md) @@ -569,7 +598,6 @@ All URIs are relative to _http://localhost_ - [GroupRequestModel](docs/GroupRequestModel.md) - [GroupResponseModel](docs/GroupResponseModel.md) - [GroupServiceAccountAccessPolicyResponseModel](docs/GroupServiceAccountAccessPolicyResponseModel.md) -- [IapCheckRequestModel](docs/IapCheckRequestModel.md) - [ImportCiphersRequestModel](docs/ImportCiphersRequestModel.md) - [ImportOrganizationCiphersRequestModel](docs/ImportOrganizationCiphersRequestModel.md) - [ImportOrganizationUsersRequestModel](docs/ImportOrganizationUsersRequestModel.md) @@ -594,6 +622,7 @@ All URIs are relative to _http://localhost_ - [OrganizationApiKeyRequestModel](docs/OrganizationApiKeyRequestModel.md) - [OrganizationApiKeyType](docs/OrganizationApiKeyType.md) - [OrganizationAutoEnrollStatusResponseModel](docs/OrganizationAutoEnrollStatusResponseModel.md) +- [OrganizationCollectionManagementUpdateRequestModel](docs/OrganizationCollectionManagementUpdateRequestModel.md) - [OrganizationConnectionRequestModel](docs/OrganizationConnectionRequestModel.md) - [OrganizationConnectionResponseModel](docs/OrganizationConnectionResponseModel.md) - [OrganizationConnectionType](docs/OrganizationConnectionType.md) @@ -603,11 +632,12 @@ All URIs are relative to _http://localhost_ - [OrganizationDomainResponseModelListResponseModel](docs/OrganizationDomainResponseModelListResponseModel.md) - [OrganizationDomainSsoDetailsRequestModel](docs/OrganizationDomainSsoDetailsRequestModel.md) - [OrganizationDomainSsoDetailsResponseModel](docs/OrganizationDomainSsoDetailsResponseModel.md) -- [OrganizationEnrollSecretsManagerRequestModel](docs/OrganizationEnrollSecretsManagerRequestModel.md) - [OrganizationKeysRequestModel](docs/OrganizationKeysRequestModel.md) - [OrganizationKeysResponseModel](docs/OrganizationKeysResponseModel.md) - [OrganizationLicense](docs/OrganizationLicense.md) +- [OrganizationPublicKeyResponseModel](docs/OrganizationPublicKeyResponseModel.md) - [OrganizationResponseModel](docs/OrganizationResponseModel.md) +- [OrganizationRisksSubscriptionFailureResponseModel](docs/OrganizationRisksSubscriptionFailureResponseModel.md) - [OrganizationSeatRequestModel](docs/OrganizationSeatRequestModel.md) - [OrganizationSponsorshipCreateRequestModel](docs/OrganizationSponsorshipCreateRequestModel.md) - [OrganizationSponsorshipRedeemRequestModel](docs/OrganizationSponsorshipRedeemRequestModel.md) @@ -644,13 +674,16 @@ All URIs are relative to _http://localhost_ - [OrganizationUserUserDetailsResponseModel](docs/OrganizationUserUserDetailsResponseModel.md) - [OrganizationUserUserDetailsResponseModelListResponseModel](docs/OrganizationUserUserDetailsResponseModelListResponseModel.md) - [OrganizationVerifyBankRequestModel](docs/OrganizationVerifyBankRequestModel.md) +- [OtherDeviceKeysUpdateRequestModel](docs/OtherDeviceKeysUpdateRequestModel.md) - [PasswordHintRequestModel](docs/PasswordHintRequestModel.md) +- [PasswordManagerPlanFeaturesResponseModel](docs/PasswordManagerPlanFeaturesResponseModel.md) - [PasswordRequestModel](docs/PasswordRequestModel.md) - [PaymentMethodType](docs/PaymentMethodType.md) - [PaymentRequestModel](docs/PaymentRequestModel.md) - [PaymentResponseModel](docs/PaymentResponseModel.md) - [PendingOrganizationAuthRequestResponseModel](docs/PendingOrganizationAuthRequestResponseModel.md) - [PendingOrganizationAuthRequestResponseModelListResponseModel](docs/PendingOrganizationAuthRequestResponseModelListResponseModel.md) +- [PeopleAccessPoliciesRequestModel](docs/PeopleAccessPoliciesRequestModel.md) - [Permissions](docs/Permissions.md) - [PlanResponseModel](docs/PlanResponseModel.md) - [PlanResponseModelListResponseModel](docs/PlanResponseModelListResponseModel.md) @@ -672,9 +705,11 @@ All URIs are relative to _http://localhost_ - [ProfileResponseModel](docs/ProfileResponseModel.md) - [ProjectAccessPoliciesResponseModel](docs/ProjectAccessPoliciesResponseModel.md) - [ProjectCreateRequestModel](docs/ProjectCreateRequestModel.md) +- [ProjectPeopleAccessPoliciesResponseModel](docs/ProjectPeopleAccessPoliciesResponseModel.md) - [ProjectResponseModel](docs/ProjectResponseModel.md) - [ProjectResponseModelListResponseModel](docs/ProjectResponseModelListResponseModel.md) - [ProjectUpdateRequestModel](docs/ProjectUpdateRequestModel.md) +- [ProtectedDeviceResponseModel](docs/ProtectedDeviceResponseModel.md) - [ProviderOrganizationAddRequestModel](docs/ProviderOrganizationAddRequestModel.md) - [ProviderOrganizationCreateRequestModel](docs/ProviderOrganizationCreateRequestModel.md) - [ProviderOrganizationOrganizationDetailsResponseModel](docs/ProviderOrganizationOrganizationDetailsResponseModel.md) @@ -700,6 +735,9 @@ All URIs are relative to _http://localhost_ - [ProviderUserUpdateRequestModel](docs/ProviderUserUpdateRequestModel.md) - [ProviderUserUserDetailsResponseModel](docs/ProviderUserUserDetailsResponseModel.md) - [ProviderUserUserDetailsResponseModelListResponseModel](docs/ProviderUserUserDetailsResponseModelListResponseModel.md) +- [PubKeyCredParam](docs/PubKeyCredParam.md) +- [PublicKeyCredentialDescriptor](docs/PublicKeyCredentialDescriptor.md) +- [PublicKeyCredentialRpEntity](docs/PublicKeyCredentialRpEntity.md) - [PublicKeyCredentialType](docs/PublicKeyCredentialType.md) - [PushRegistrationRequestModel](docs/PushRegistrationRequestModel.md) - [PushSendRequestModel](docs/PushSendRequestModel.md) @@ -707,6 +745,7 @@ All URIs are relative to _http://localhost_ - [PushUpdateRequestModel](docs/PushUpdateRequestModel.md) - [RegisterRequestModel](docs/RegisterRequestModel.md) - [RegisterResponseModel](docs/RegisterResponseModel.md) +- [ResetPasswordWithOrgIdRequestModel](docs/ResetPasswordWithOrgIdRequestModel.md) - [ResponseData](docs/ResponseData.md) - [RevokeAccessTokensRequest](docs/RevokeAccessTokensRequest.md) - [Saml2BindingType](docs/Saml2BindingType.md) @@ -719,6 +758,8 @@ All URIs are relative to _http://localhost_ - [SecretVerificationRequestModel](docs/SecretVerificationRequestModel.md) - [SecretWithProjectsInnerProject](docs/SecretWithProjectsInnerProject.md) - [SecretWithProjectsListResponseModel](docs/SecretWithProjectsListResponseModel.md) +- [SecretsManagerPlanFeaturesResponseModel](docs/SecretsManagerPlanFeaturesResponseModel.md) +- [SecretsManagerSubscribeRequestModel](docs/SecretsManagerSubscribeRequestModel.md) - [SecretsManagerSubscriptionUpdateRequestModel](docs/SecretsManagerSubscriptionUpdateRequestModel.md) - [SecretsWithProjectsInnerSecret](docs/SecretsWithProjectsInnerSecret.md) - [SecureNoteType](docs/SecureNoteType.md) @@ -735,12 +776,13 @@ All URIs are relative to _http://localhost_ - [SendType](docs/SendType.md) - [SendWithIdRequestModel](docs/SendWithIdRequestModel.md) - [ServerConfigResponseModel](docs/ServerConfigResponseModel.md) -- [ServiceAccountAccessPoliciesResponseModel](docs/ServiceAccountAccessPoliciesResponseModel.md) - [ServiceAccountCreateRequestModel](docs/ServiceAccountCreateRequestModel.md) +- [ServiceAccountPeopleAccessPoliciesResponseModel](docs/ServiceAccountPeopleAccessPoliciesResponseModel.md) - [ServiceAccountProjectAccessPolicyResponseModel](docs/ServiceAccountProjectAccessPolicyResponseModel.md) - [ServiceAccountProjectAccessPolicyResponseModelListResponseModel](docs/ServiceAccountProjectAccessPolicyResponseModelListResponseModel.md) - [ServiceAccountResponseModel](docs/ServiceAccountResponseModel.md) -- [ServiceAccountResponseModelListResponseModel](docs/ServiceAccountResponseModelListResponseModel.md) +- [ServiceAccountSecretsDetailsResponseModel](docs/ServiceAccountSecretsDetailsResponseModel.md) +- [ServiceAccountSecretsDetailsResponseModelListResponseModel](docs/ServiceAccountSecretsDetailsResponseModelListResponseModel.md) - [ServiceAccountUpdateRequestModel](docs/ServiceAccountUpdateRequestModel.md) - [SetKeyConnectorKeyRequestModel](docs/SetKeyConnectorKeyRequestModel.md) - [SetPasswordRequestModel](docs/SetPasswordRequestModel.md) @@ -773,6 +815,7 @@ All URIs are relative to _http://localhost_ - [TwoFactorWebAuthnResponseModel](docs/TwoFactorWebAuthnResponseModel.md) - [TwoFactorYubiKeyResponseModel](docs/TwoFactorYubiKeyResponseModel.md) - [UpdateAvatarRequestModel](docs/UpdateAvatarRequestModel.md) +- [UpdateDevicesTrustRequestModel](docs/UpdateDevicesTrustRequestModel.md) - [UpdateDomainsRequestModel](docs/UpdateDomainsRequestModel.md) - [UpdateKeyRequestModel](docs/UpdateKeyRequestModel.md) - [UpdateProfileRequestModel](docs/UpdateProfileRequestModel.md) @@ -787,9 +830,17 @@ All URIs are relative to _http://localhost_ - [UserLicense](docs/UserLicense.md) - [UserProjectAccessPolicyResponseModel](docs/UserProjectAccessPolicyResponseModel.md) - [UserServiceAccountAccessPolicyResponseModel](docs/UserServiceAccountAccessPolicyResponseModel.md) +- [UserVerificationRequirement](docs/UserVerificationRequirement.md) - [VerifyDeleteRecoverRequestModel](docs/VerifyDeleteRecoverRequestModel.md) - [VerifyEmailRequestModel](docs/VerifyEmailRequestModel.md) - [VerifyOtpRequestModel](docs/VerifyOtpRequestModel.md) +- [WebAuthnCredentialCreateOptionsResponseModel](docs/WebAuthnCredentialCreateOptionsResponseModel.md) +- [WebAuthnCredentialResponseModel](docs/WebAuthnCredentialResponseModel.md) +- [WebAuthnCredentialResponseModelListResponseModel](docs/WebAuthnCredentialResponseModelListResponseModel.md) +- [WebAuthnLoginAssertionOptionsResponseModel](docs/WebAuthnLoginAssertionOptionsResponseModel.md) +- [WebAuthnLoginCredentialCreateRequestModel](docs/WebAuthnLoginCredentialCreateRequestModel.md) +- [WebAuthnLoginCredentialUpdateRequestModel](docs/WebAuthnLoginCredentialUpdateRequestModel.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 7e538c0b4..9f1135beb 100644 --- a/crates/bitwarden-api-api/src/apis/access_policies_api.rs +++ b/crates/bitwarden-api-api/src/apis/access_policies_api.rs @@ -27,21 +27,24 @@ pub enum AccessPoliciesIdPutError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_access_policies_people_potential_grantees_get`] +/// struct for typed errors of method +/// [`organizations_id_access_policies_people_potential_grantees_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsIdAccessPoliciesPeoplePotentialGranteesGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_access_policies_projects_potential_grantees_get`] +/// struct for typed errors of method +/// [`organizations_id_access_policies_projects_potential_grantees_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsIdAccessPoliciesProjectsPotentialGranteesGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_access_policies_service_accounts_potential_grantees_get`] +/// struct for typed errors of method +/// [`organizations_id_access_policies_service_accounts_potential_grantees_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsIdAccessPoliciesServiceAccountsPotentialGranteesGetError { @@ -55,6 +58,20 @@ pub enum ProjectsIdAccessPoliciesGetError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`projects_id_access_policies_people_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ProjectsIdAccessPoliciesPeopleGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`projects_id_access_policies_people_put`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ProjectsIdAccessPoliciesPeoplePutError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`projects_id_access_policies_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -62,17 +79,17 @@ pub enum ProjectsIdAccessPoliciesPostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`service_accounts_id_access_policies_get`] +/// struct for typed errors of method [`service_accounts_id_access_policies_people_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum ServiceAccountsIdAccessPoliciesGetError { +pub enum ServiceAccountsIdAccessPoliciesPeopleGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`service_accounts_id_access_policies_post`] +/// struct for typed errors of method [`service_accounts_id_access_policies_people_put`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum ServiceAccountsIdAccessPoliciesPostError { +pub enum ServiceAccountsIdAccessPoliciesPeoplePutError { UnknownValue(serde_json::Value), } @@ -370,6 +387,102 @@ pub async fn projects_id_access_policies_get( } } +pub async fn projects_id_access_policies_people_get( + configuration: &configuration::Configuration, + id: uuid::Uuid, +) -> Result< + crate::models::ProjectPeopleAccessPoliciesResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/projects/{id}/access-policies/people", + local_var_configuration.base_path, + id = crate::apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn projects_id_access_policies_people_put( + configuration: &configuration::Configuration, + id: uuid::Uuid, + people_access_policies_request_model: Option, +) -> Result< + crate::models::ProjectPeopleAccessPoliciesResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/projects/{id}/access-policies/people", + local_var_configuration.base_path, + id = crate::apis::urlencode(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(&people_access_policies_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 projects_id_access_policies_post( configuration: &configuration::Configuration, id: uuid::Uuid, @@ -419,19 +532,19 @@ pub async fn projects_id_access_policies_post( } } -pub async fn service_accounts_id_access_policies_get( +pub async fn service_accounts_id_access_policies_people_get( configuration: &configuration::Configuration, id: uuid::Uuid, ) -> Result< - crate::models::ServiceAccountAccessPoliciesResponseModel, - Error, + crate::models::ServiceAccountPeopleAccessPoliciesResponseModel, + Error, > { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; let local_var_uri_str = format!( - "{}/service-accounts/{id}/access-policies", + "{}/service-accounts/{id}/access-policies/people", local_var_configuration.base_path, id = crate::apis::urlencode(id.to_string()) ); @@ -455,7 +568,7 @@ pub async fn service_accounts_id_access_policies_get( 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 = + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); let local_var_error = ResponseContent { status: local_var_status, @@ -466,25 +579,25 @@ pub async fn service_accounts_id_access_policies_get( } } -pub async fn service_accounts_id_access_policies_post( +pub async fn service_accounts_id_access_policies_people_put( configuration: &configuration::Configuration, id: uuid::Uuid, - access_policies_create_request: Option, + people_access_policies_request_model: Option, ) -> Result< - crate::models::ServiceAccountAccessPoliciesResponseModel, - Error, + crate::models::ServiceAccountPeopleAccessPoliciesResponseModel, + Error, > { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; let local_var_uri_str = format!( - "{}/service-accounts/{id}/access-policies", + "{}/service-accounts/{id}/access-policies/people", 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()); + 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 = @@ -493,7 +606,7 @@ pub async fn service_accounts_id_access_policies_post( 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(&access_policies_create_request); + local_var_req_builder = local_var_req_builder.json(&people_access_policies_request_model); let local_var_req = local_var_req_builder.build()?; let local_var_resp = local_var_client.execute(local_var_req).await?; @@ -504,7 +617,7 @@ pub async fn service_accounts_id_access_policies_post( 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 = + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); let local_var_error = ResponseContent { status: local_var_status, diff --git a/crates/bitwarden-api-api/src/apis/accounts_api.rs b/crates/bitwarden-api-api/src/apis/accounts_api.rs index 9e3349409..23b1c1d85 100644 --- a/crates/bitwarden-api-api/src/apis/accounts_api.rs +++ b/crates/bitwarden-api-api/src/apis/accounts_api.rs @@ -90,13 +90,6 @@ pub enum AccountsEmailTokenPostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`accounts_iap_check_post`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum AccountsIapCheckPostError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`accounts_kdf_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -787,47 +780,6 @@ pub async fn accounts_email_token_post( } } -pub async fn accounts_iap_check_post( - configuration: &configuration::Configuration, - iap_check_request_model: Option, -) -> Result<(), Error> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!("{}/accounts/iap-check", 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(&iap_check_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_kdf_post( configuration: &configuration::Configuration, kdf_request_model: Option, diff --git a/crates/bitwarden-api-api/src/apis/auth_requests_api.rs b/crates/bitwarden-api-api/src/apis/auth_requests_api.rs index 476a94129..39c4dd7b4 100644 --- a/crates/bitwarden-api-api/src/apis/auth_requests_api.rs +++ b/crates/bitwarden-api-api/src/apis/auth_requests_api.rs @@ -13,6 +13,13 @@ use reqwest; use super::{configuration, Error}; use crate::apis::ResponseContent; +/// struct for typed errors of method [`auth_requests_admin_request_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AuthRequestsAdminRequestPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`auth_requests_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -48,6 +55,50 @@ pub enum AuthRequestsPostError { UnknownValue(serde_json::Value), } +pub async fn auth_requests_admin_request_post( + configuration: &configuration::Configuration, + auth_request_create_request_model: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/auth-requests/admin-request", + local_var_configuration.base_path + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&auth_request_create_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn auth_requests_get( configuration: &configuration::Configuration, ) -> Result> { diff --git a/crates/bitwarden-api-api/src/apis/ciphers_api.rs b/crates/bitwarden-api-api/src/apis/ciphers_api.rs index 4ca7f4969..5a1466c75 100644 --- a/crates/bitwarden-api-api/src/apis/ciphers_api.rs +++ b/crates/bitwarden-api-api/src/apis/ciphers_api.rs @@ -1072,7 +1072,7 @@ pub async fn ciphers_id_attachment_attachment_id_admin_delete( pub async fn ciphers_id_attachment_attachment_id_delete( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, attachment_id: &str, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1164,7 +1164,7 @@ pub async fn ciphers_id_attachment_attachment_id_delete_admin_post( pub async fn ciphers_id_attachment_attachment_id_delete_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, attachment_id: &str, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1210,7 +1210,7 @@ pub async fn ciphers_id_attachment_attachment_id_delete_post( pub async fn ciphers_id_attachment_attachment_id_get( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, attachment_id: &str, ) -> Result> { @@ -1257,7 +1257,7 @@ pub async fn ciphers_id_attachment_attachment_id_get( pub async fn ciphers_id_attachment_attachment_id_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, attachment_id: &str, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1303,7 +1303,7 @@ pub async fn ciphers_id_attachment_attachment_id_post( pub async fn ciphers_id_attachment_attachment_id_renew_get( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, attachment_id: &str, ) -> Result< crate::models::AttachmentUploadDataResponseModel, @@ -1403,7 +1403,7 @@ pub async fn ciphers_id_attachment_attachment_id_share_post( pub async fn ciphers_id_attachment_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result> { let local_var_configuration = configuration; @@ -1447,7 +1447,7 @@ pub async fn ciphers_id_attachment_post( pub async fn ciphers_id_attachment_v2_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, attachment_request_model: Option, ) -> Result> { @@ -1586,7 +1586,7 @@ pub async fn ciphers_id_collections_admin_put( pub async fn ciphers_id_collections_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, cipher_collections_request_model: Option, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1632,7 +1632,7 @@ pub async fn ciphers_id_collections_post( pub async fn ciphers_id_collections_put( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, cipher_collections_request_model: Option, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1678,7 +1678,7 @@ pub async fn ciphers_id_collections_put( pub async fn ciphers_id_delete( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1810,7 +1810,7 @@ pub async fn ciphers_id_delete_admin_put( pub async fn ciphers_id_delete_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1854,7 +1854,7 @@ pub async fn ciphers_id_delete_post( pub async fn ciphers_id_delete_put( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -1898,7 +1898,7 @@ pub async fn ciphers_id_delete_put( pub async fn ciphers_id_details_get( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result> { let local_var_configuration = configuration; @@ -1942,7 +1942,7 @@ pub async fn ciphers_id_details_get( pub async fn ciphers_id_full_details_get( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result> { let local_var_configuration = configuration; @@ -1986,7 +1986,7 @@ pub async fn ciphers_id_full_details_get( pub async fn ciphers_id_get( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result> { let local_var_configuration = configuration; @@ -2030,7 +2030,7 @@ pub async fn ciphers_id_get( pub async fn ciphers_id_partial_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, cipher_partial_request_model: Option, ) -> Result> { let local_var_configuration = configuration; @@ -2076,7 +2076,7 @@ pub async fn ciphers_id_partial_post( pub async fn ciphers_id_partial_put( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, cipher_partial_request_model: Option, ) -> Result> { let local_var_configuration = configuration; @@ -2258,7 +2258,7 @@ pub async fn ciphers_id_restore_admin_put( pub async fn ciphers_id_restore_put( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, ) -> Result> { let local_var_configuration = configuration; @@ -2302,7 +2302,7 @@ pub async fn ciphers_id_restore_put( pub async fn ciphers_id_share_post( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, cipher_share_request_model: Option, ) -> Result> { let local_var_configuration = configuration; @@ -2348,7 +2348,7 @@ pub async fn ciphers_id_share_post( pub async fn ciphers_id_share_put( configuration: &configuration::Configuration, - id: &str, + id: uuid::Uuid, cipher_share_request_model: Option, ) -> Result> { let local_var_configuration = configuration; diff --git a/crates/bitwarden-api-api/src/apis/collections_api.rs b/crates/bitwarden-api-api/src/apis/collections_api.rs index 02ac6eb16..d6c302538 100644 --- a/crates/bitwarden-api-api/src/apis/collections_api.rs +++ b/crates/bitwarden-api-api/src/apis/collections_api.rs @@ -20,6 +20,13 @@ pub enum CollectionsGetError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_org_id_collections_bulk_access_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsOrgIdCollectionsBulkAccessPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_org_id_collections_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -62,7 +69,8 @@ pub enum OrganizationsOrgIdCollectionsIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_collections_id_delete_user_org_user_id_post`] +/// struct for typed errors of method +/// [`organizations_org_id_collections_id_delete_user_org_user_id_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdCollectionsIdDeleteUserOrgUserIdPostError { @@ -97,7 +105,8 @@ pub enum OrganizationsOrgIdCollectionsIdPutError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_collections_id_user_org_user_id_delete`] +/// struct for typed errors of method +/// [`organizations_org_id_collections_id_user_org_user_id_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdCollectionsIdUserOrgUserIdDeleteError { @@ -167,9 +176,55 @@ pub async fn collections_get( } } +pub async fn organizations_org_id_collections_bulk_access_post( + configuration: &configuration::Configuration, + org_id: uuid::Uuid, + bulk_collection_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!( + "{}/organizations/{orgId}/collections/bulk-access", + 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(&bulk_collection_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)) + } +} + pub async fn organizations_org_id_collections_delete( configuration: &configuration::Configuration, - org_id: &str, + org_id: uuid::Uuid, collection_bulk_delete_request_model: Option, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -215,7 +270,7 @@ pub async fn organizations_org_id_collections_delete( pub async fn organizations_org_id_collections_delete_post( configuration: &configuration::Configuration, - org_id: &str, + org_id: uuid::Uuid, collection_bulk_delete_request_model: Option, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -447,9 +502,9 @@ pub async fn organizations_org_id_collections_id_delete_post( pub async fn organizations_org_id_collections_id_delete_user_org_user_id_post( configuration: &configuration::Configuration, - org_id: &str, - id: &str, - org_user_id: &str, + org_id: uuid::Uuid, + id: uuid::Uuid, + org_user_id: uuid::Uuid, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -689,9 +744,9 @@ pub async fn organizations_org_id_collections_id_put( pub async fn organizations_org_id_collections_id_user_org_user_id_delete( configuration: &configuration::Configuration, - org_id: &str, - id: &str, - org_user_id: &str, + org_id: uuid::Uuid, + id: uuid::Uuid, + org_user_id: uuid::Uuid, ) -> Result<(), Error> { let local_var_configuration = configuration; diff --git a/crates/bitwarden-api-api/src/apis/configuration.rs b/crates/bitwarden-api-api/src/apis/configuration.rs index 6b5e1c3a3..83d56c8a5 100644 --- a/crates/bitwarden-api-api/src/apis/configuration.rs +++ b/crates/bitwarden-api-api/src/apis/configuration.rs @@ -8,8 +8,6 @@ * Generated by: https://openapi-generator.tech */ -use reqwest; - #[derive(Debug, Clone)] pub struct Configuration { pub base_path: String, diff --git a/crates/bitwarden-api-api/src/apis/devices_api.rs b/crates/bitwarden-api-api/src/apis/devices_api.rs index e7be0685e..71cfdaade 100644 --- a/crates/bitwarden-api-api/src/apis/devices_api.rs +++ b/crates/bitwarden-api-api/src/apis/devices_api.rs @@ -13,13 +13,6 @@ use reqwest; use super::{configuration, Error}; use crate::apis::ResponseContent; -/// struct for typed errors of method [`devices_exist_by_types_post`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum DevicesExistByTypesPostError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`devices_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -111,6 +104,13 @@ pub enum DevicesIdentifierKeysPutError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`devices_identifier_retrieve_keys_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DevicesIdentifierRetrieveKeysPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`devices_knowndevice_email_identifier_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -132,48 +132,11 @@ pub enum DevicesPostError { UnknownValue(serde_json::Value), } -pub async fn devices_exist_by_types_post( - configuration: &configuration::Configuration, - device_type: Option>, -) -> Result> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!( - "{}/devices/exist-by-types", - local_var_configuration.base_path - ); - let mut local_var_req_builder = - local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); - - if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { - local_var_req_builder = - local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); - } - if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { - local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); - }; - local_var_req_builder = local_var_req_builder.json(&device_type); - - let local_var_req = local_var_req_builder.build()?; - let local_var_resp = local_var_client.execute(local_var_req).await?; - - let local_var_status = local_var_resp.status(); - let local_var_content = local_var_resp.text().await?; - - if !local_var_status.is_client_error() && !local_var_status.is_server_error() { - serde_json::from_str(&local_var_content).map_err(Error::from) - } else { - let local_var_entity: Option = - serde_json::from_str(&local_var_content).ok(); - let local_var_error = ResponseContent { - status: local_var_status, - content: local_var_content, - entity: local_var_entity, - }; - Err(Error::ResponseError(local_var_error)) - } +/// struct for typed errors of method [`devices_update_trust_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DevicesUpdateTrustPostError { + UnknownValue(serde_json::Value), } pub async fn devices_get( @@ -755,6 +718,55 @@ pub async fn devices_identifier_keys_put( } } +pub async fn devices_identifier_retrieve_keys_post( + configuration: &configuration::Configuration, + identifier: &str, + secret_verification_request_model: Option, +) -> Result< + crate::models::ProtectedDeviceResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/devices/{identifier}/retrieve-keys", + local_var_configuration.base_path, + identifier = crate::apis::urlencode(identifier.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&secret_verification_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn devices_knowndevice_email_identifier_get( configuration: &configuration::Configuration, email: &str, @@ -803,8 +815,8 @@ pub async fn devices_knowndevice_email_identifier_get( pub async fn devices_knowndevice_get( configuration: &configuration::Configuration, - x_request_email: Option<&str>, - x_device_identifier: Option<&str>, + x_request_email: &str, + x_device_identifier: &str, ) -> Result> { let local_var_configuration = configuration; @@ -818,14 +830,10 @@ pub async fn devices_knowndevice_get( local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } - if let Some(local_var_param_value) = x_request_email { - local_var_req_builder = - local_var_req_builder.header("x-Request-Email", local_var_param_value.to_string()); - } - if let Some(local_var_param_value) = x_device_identifier { - local_var_req_builder = - local_var_req_builder.header("x-Device-Identifier", local_var_param_value.to_string()); - } + local_var_req_builder = + local_var_req_builder.header("x-Request-Email", x_request_email.to_string()); + local_var_req_builder = + local_var_req_builder.header("x-Device-Identifier", x_device_identifier.to_string()); 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()); }; @@ -890,3 +898,44 @@ pub async fn devices_post( Err(Error::ResponseError(local_var_error)) } } + +pub async fn devices_update_trust_post( + configuration: &configuration::Configuration, + update_devices_trust_request_model: Option, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/devices/update-trust", local_var_configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&update_devices_trust_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + Ok(()) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/crates/bitwarden-api-api/src/apis/groups_api.rs b/crates/bitwarden-api-api/src/apis/groups_api.rs index 46ed9afa5..5918989f4 100644 --- a/crates/bitwarden-api-api/src/apis/groups_api.rs +++ b/crates/bitwarden-api-api/src/apis/groups_api.rs @@ -48,7 +48,8 @@ pub enum OrganizationsOrgIdGroupsIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_groups_id_delete_user_org_user_id_post`] +/// struct for typed errors of method +/// [`organizations_org_id_groups_id_delete_user_org_user_id_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdGroupsIdDeleteUserOrgUserIdPostError { @@ -205,7 +206,7 @@ pub async fn organizations_org_id_groups_delete_post( pub async fn organizations_org_id_groups_get( configuration: &configuration::Configuration, - org_id: &str, + org_id: uuid::Uuid, ) -> Result< crate::models::GroupDetailsResponseModelListResponseModel, Error, diff --git a/crates/bitwarden-api-api/src/apis/mod.rs b/crates/bitwarden-api-api/src/apis/mod.rs index 623d5d0b6..b99dc592d 100644 --- a/crates/bitwarden-api-api/src/apis/mod.rs +++ b/crates/bitwarden-api-api/src/apis/mod.rs @@ -1,5 +1,4 @@ -use std::error; -use std::fmt; +use std::{error, fmt}; #[derive(Debug, Clone)] pub struct ResponseContent { @@ -61,6 +60,37 @@ pub fn urlencode>(s: T) -> String { ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() } +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + } + serde_json::Value::String(s) => { + params.push((format!("{}[{}]", prefix, key), s.clone())) + } + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + pub mod access_policies_api; pub mod accounts_api; pub mod accounts_billing_api; @@ -94,6 +124,7 @@ pub mod provider_users_api; pub mod providers_api; pub mod push_api; pub mod secrets_api; +pub mod secrets_manager_events_api; pub mod secrets_manager_porting_api; pub mod self_hosted_organization_licenses_api; pub mod self_hosted_organization_sponsorships_api; @@ -104,5 +135,6 @@ pub mod sync_api; pub mod trash_api; pub mod two_factor_api; pub mod users_api; +pub mod web_authn_api; pub mod configuration; diff --git a/crates/bitwarden-api-api/src/apis/organization_connections_api.rs b/crates/bitwarden-api-api/src/apis/organization_connections_api.rs index 8bf41ffd1..ee75716dc 100644 --- a/crates/bitwarden-api-api/src/apis/organization_connections_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_connections_api.rs @@ -20,14 +20,16 @@ pub enum OrganizationsConnectionsEnabledGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_connections_organization_connection_id_delete`] +/// struct for typed errors of method +/// [`organizations_connections_organization_connection_id_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsConnectionsOrganizationConnectionIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_connections_organization_connection_id_delete_post`] +/// struct for typed errors of method +/// [`organizations_connections_organization_connection_id_delete_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsConnectionsOrganizationConnectionIdDeletePostError { diff --git a/crates/bitwarden-api-api/src/apis/organization_domain_api.rs b/crates/bitwarden-api-api/src/apis/organization_domain_api.rs index 4e10ff78f..c55e95f2a 100644 --- a/crates/bitwarden-api-api/src/apis/organization_domain_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_domain_api.rs @@ -114,7 +114,7 @@ pub async fn organizations_domain_sso_details_post( pub async fn organizations_org_id_domain_get( configuration: &configuration::Configuration, - org_id: &str, + org_id: uuid::Uuid, ) -> Result< crate::models::OrganizationDomainResponseModelListResponseModel, Error, @@ -161,8 +161,8 @@ pub async fn organizations_org_id_domain_get( pub async fn organizations_org_id_domain_id_delete( configuration: &configuration::Configuration, - org_id: &str, - id: &str, + org_id: uuid::Uuid, + id: uuid::Uuid, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -207,8 +207,8 @@ pub async fn organizations_org_id_domain_id_delete( pub async fn organizations_org_id_domain_id_get( configuration: &configuration::Configuration, - org_id: &str, - id: &str, + org_id: uuid::Uuid, + id: uuid::Uuid, ) -> Result> { let local_var_configuration = configuration; @@ -254,8 +254,8 @@ pub async fn organizations_org_id_domain_id_get( pub async fn organizations_org_id_domain_id_remove_post( configuration: &configuration::Configuration, - org_id: &str, - id: &str, + org_id: uuid::Uuid, + id: uuid::Uuid, ) -> Result<(), Error> { let local_var_configuration = configuration; @@ -300,8 +300,8 @@ pub async fn organizations_org_id_domain_id_remove_post( pub async fn organizations_org_id_domain_id_verify_post( configuration: &configuration::Configuration, - org_id: &str, - id: &str, + org_id: uuid::Uuid, + id: uuid::Uuid, ) -> Result< crate::models::OrganizationDomainResponseModel, Error, @@ -349,7 +349,7 @@ pub async fn organizations_org_id_domain_id_verify_post( pub async fn organizations_org_id_domain_post( configuration: &configuration::Configuration, - org_id: &str, + org_id: uuid::Uuid, organization_domain_request_model: Option, ) -> Result> { diff --git a/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs b/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs index 203174fdf..e3f1ccf24 100644 --- a/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs @@ -27,21 +27,24 @@ pub enum OrganizationSponsorshipSponsoredSponsoredOrgIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsored_sponsored_org_id_remove_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsored_sponsored_org_id_remove_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoredSponsoredOrgIdRemovePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoringOrgIdFamiliesForEnterprisePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_resend_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_resend_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoringOrgIdFamiliesForEnterpriseResendPostError { @@ -62,7 +65,8 @@ pub enum OrganizationSponsorshipSponsoringOrganizationIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsoring_organization_id_delete_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsoring_organization_id_delete_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoringOrganizationIdDeletePostError { diff --git a/crates/bitwarden-api-api/src/apis/organization_users_api.rs b/crates/bitwarden-api-api/src/apis/organization_users_api.rs index f81290a86..ef9fd864f 100644 --- a/crates/bitwarden-api-api/src/apis/organization_users_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_users_api.rs @@ -174,14 +174,16 @@ pub enum OrganizationsOrgIdUsersInvitePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_users_organization_user_id_accept_init_post`] +/// struct for typed errors of method +/// [`organizations_org_id_users_organization_user_id_accept_init_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdUsersOrganizationUserIdAcceptInitPostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_users_organization_user_id_accept_post`] +/// struct for typed errors of method +/// [`organizations_org_id_users_organization_user_id_accept_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdUsersOrganizationUserIdAcceptPostError { @@ -230,7 +232,8 @@ pub enum OrganizationsOrgIdUsersRevokePutError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_users_user_id_reset_password_enrollment_put`] +/// struct for typed errors of method +/// [`organizations_org_id_users_user_id_reset_password_enrollment_put`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdUsersUserIdResetPasswordEnrollmentPutError { @@ -391,10 +394,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_patch( configuration: &configuration::Configuration, org_id: uuid::Uuid, organization_user_bulk_request_model: Option, -) -> Result< - crate::models::OrganizationUserBulkResponseModelListResponseModel, - Error, -> { +) -> Result<(), Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; @@ -423,7 +423,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_patch( let local_var_content = local_var_resp.text().await?; if !local_var_status.is_client_error() && !local_var_status.is_server_error() { - serde_json::from_str(&local_var_content).map_err(Error::from) + Ok(()) } else { let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); @@ -440,10 +440,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_put( configuration: &configuration::Configuration, org_id: uuid::Uuid, organization_user_bulk_request_model: Option, -) -> Result< - crate::models::OrganizationUserBulkResponseModelListResponseModel, - Error, -> { +) -> Result<(), Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; @@ -472,7 +469,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_put( let local_var_content = local_var_resp.text().await?; if !local_var_status.is_client_error() && !local_var_status.is_server_error() { - serde_json::from_str(&local_var_content).map_err(Error::from) + Ok(()) } else { let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); @@ -487,7 +484,7 @@ pub async fn organizations_org_id_users_enable_secrets_manager_put( pub async fn organizations_org_id_users_get( configuration: &configuration::Configuration, - org_id: &str, + org_id: uuid::Uuid, include_groups: Option, include_collections: Option, ) -> Result< @@ -1761,8 +1758,8 @@ pub async fn organizations_org_id_users_revoke_put( pub async fn organizations_org_id_users_user_id_reset_password_enrollment_put( configuration: &configuration::Configuration, - org_id: &str, - user_id: &str, + org_id: uuid::Uuid, + user_id: uuid::Uuid, organization_user_reset_password_enrollment_request_model: Option< crate::models::OrganizationUserResetPasswordEnrollmentRequestModel, >, diff --git a/crates/bitwarden-api-api/src/apis/organizations_api.rs b/crates/bitwarden-api-api/src/apis/organizations_api.rs index 1cd531b2f..cd9c3326a 100644 --- a/crates/bitwarden-api-api/src/apis/organizations_api.rs +++ b/crates/bitwarden-api-api/src/apis/organizations_api.rs @@ -48,6 +48,13 @@ pub enum OrganizationsIdCancelPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_id_collection_management_put`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsIdCollectionManagementPutError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_id_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -62,10 +69,10 @@ pub enum OrganizationsIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_enroll_secrets_manager_post`] +/// struct for typed errors of method [`organizations_id_enable_collection_enhancements_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] -pub enum OrganizationsIdEnrollSecretsManagerPostError { +pub enum OrganizationsIdEnableCollectionEnhancementsPostError { UnknownValue(serde_json::Value), } @@ -125,6 +132,13 @@ pub enum OrganizationsIdPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_id_public_key_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsIdPublicKeyGetError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_id_put`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -139,6 +153,13 @@ pub enum OrganizationsIdReinstatePostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_id_risks_subscription_failure_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsIdRisksSubscriptionFailureGetError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_id_rotate_api_key_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -181,6 +202,13 @@ pub enum OrganizationsIdStoragePostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_id_subscribe_secrets_manager_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsIdSubscribeSecretsManagerPostError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_id_subscription_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -457,6 +485,58 @@ pub async fn organizations_id_cancel_post( } } +pub async fn organizations_id_collection_management_put( + configuration: &configuration::Configuration, + id: uuid::Uuid, + organization_collection_management_update_request_model: Option< + crate::models::OrganizationCollectionManagementUpdateRequestModel, + >, +) -> Result< + crate::models::OrganizationResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{id}/collection-management", + local_var_configuration.base_path, + id = crate::apis::urlencode(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(&organization_collection_management_update_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn organizations_id_delete( configuration: &configuration::Configuration, id: &str, @@ -549,19 +629,16 @@ pub async fn organizations_id_delete_post( } } -pub async fn organizations_id_enroll_secrets_manager_post( +pub async fn organizations_id_enable_collection_enhancements_post( configuration: &configuration::Configuration, id: uuid::Uuid, - organization_enroll_secrets_manager_request_model: Option< - crate::models::OrganizationEnrollSecretsManagerRequestModel, - >, -) -> Result<(), Error> { +) -> Result<(), Error> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; let local_var_uri_str = format!( - "{}/organizations/{id}/enroll-secrets-manager", + "{}/organizations/{id}/enable-collection-enhancements", local_var_configuration.base_path, id = crate::apis::urlencode(id.to_string()) ); @@ -575,8 +652,6 @@ pub async fn organizations_id_enroll_secrets_manager_post( if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); }; - local_var_req_builder = - local_var_req_builder.json(&organization_enroll_secrets_manager_request_model); let local_var_req = local_var_req_builder.build()?; let local_var_resp = local_var_client.execute(local_var_req).await?; @@ -587,7 +662,7 @@ pub async fn organizations_id_enroll_secrets_manager_post( if !local_var_status.is_client_error() && !local_var_status.is_server_error() { Ok(()) } else { - let local_var_entity: Option = + let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); let local_var_error = ResponseContent { status: local_var_status, @@ -693,7 +768,7 @@ pub async fn organizations_id_import_post( pub async fn organizations_id_keys_get( configuration: &configuration::Configuration, id: &str, -) -> Result> { +) -> Result> { let local_var_configuration = configuration; let local_var_client = &local_var_configuration.client; @@ -965,6 +1040,53 @@ pub async fn organizations_id_post( } } +pub async fn organizations_id_public_key_get( + configuration: &configuration::Configuration, + id: &str, +) -> Result< + crate::models::OrganizationPublicKeyResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{id}/public-key", + local_var_configuration.base_path, + id = crate::apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn organizations_id_put( configuration: &configuration::Configuration, id: &str, @@ -1055,6 +1177,53 @@ pub async fn organizations_id_reinstate_post( } } +pub async fn organizations_id_risks_subscription_failure_get( + configuration: &configuration::Configuration, + id: uuid::Uuid, +) -> Result< + crate::models::OrganizationRisksSubscriptionFailureResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{id}/risks-subscription-failure", + local_var_configuration.base_path, + id = crate::apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn organizations_id_rotate_api_key_post( configuration: &configuration::Configuration, id: &str, @@ -1332,6 +1501,57 @@ pub async fn organizations_id_storage_post( } } +pub async fn organizations_id_subscribe_secrets_manager_post( + configuration: &configuration::Configuration, + id: uuid::Uuid, + secrets_manager_subscribe_request_model: Option< + crate::models::SecretsManagerSubscribeRequestModel, + >, +) -> Result< + crate::models::ProfileOrganizationResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{id}/subscribe-secrets-manager", + local_var_configuration.base_path, + id = crate::apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&secrets_manager_subscribe_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn organizations_id_subscription_get( configuration: &configuration::Configuration, id: &str, diff --git a/crates/bitwarden-api-api/src/apis/plans_api.rs b/crates/bitwarden-api-api/src/apis/plans_api.rs index 83a9da2b9..e0430ac66 100644 --- a/crates/bitwarden-api-api/src/apis/plans_api.rs +++ b/crates/bitwarden-api-api/src/apis/plans_api.rs @@ -13,13 +13,6 @@ use reqwest; use super::{configuration, Error}; use crate::apis::ResponseContent; -/// struct for typed errors of method [`plans_all_get`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum PlansAllGetError { - UnknownValue(serde_json::Value), -} - /// struct for typed errors of method [`plans_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -34,52 +27,6 @@ pub enum PlansSalesTaxRatesGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`plans_sm_plans_get`] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum PlansSmPlansGetError { - UnknownValue(serde_json::Value), -} - -pub async fn plans_all_get( - configuration: &configuration::Configuration, -) -> Result> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!("{}/plans/all", 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 plans_get( configuration: &configuration::Configuration, ) -> Result> { @@ -160,42 +107,3 @@ pub async fn plans_sales_tax_rates_get( Err(Error::ResponseError(local_var_error)) } } - -pub async fn plans_sm_plans_get( - configuration: &configuration::Configuration, -) -> Result> { - let local_var_configuration = configuration; - - let local_var_client = &local_var_configuration.client; - - let local_var_uri_str = format!("{}/plans/sm-plans", 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)) - } -} diff --git a/crates/bitwarden-api-api/src/apis/policies_api.rs b/crates/bitwarden-api-api/src/apis/policies_api.rs index bfec61923..1ac26b251 100644 --- a/crates/bitwarden-api-api/src/apis/policies_api.rs +++ b/crates/bitwarden-api-api/src/apis/policies_api.rs @@ -27,6 +27,13 @@ pub enum OrganizationsOrgIdPoliciesInvitedUserGetError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`organizations_org_id_policies_master_password_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum OrganizationsOrgIdPoliciesMasterPasswordGetError { + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`organizations_org_id_policies_token_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -97,8 +104,8 @@ pub async fn organizations_org_id_policies_get( pub async fn organizations_org_id_policies_invited_user_get( configuration: &configuration::Configuration, - org_id: &str, - user_id: Option<&str>, + org_id: uuid::Uuid, + user_id: Option, ) -> Result< crate::models::PolicyResponseModelListResponseModel, Error, @@ -147,12 +154,59 @@ pub async fn organizations_org_id_policies_invited_user_get( } } +pub async fn organizations_org_id_policies_master_password_get( + configuration: &configuration::Configuration, + org_id: uuid::Uuid, +) -> Result< + crate::models::PolicyResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/organizations/{orgId}/policies/master-password", + 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::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + pub async fn organizations_org_id_policies_token_get( configuration: &configuration::Configuration, - org_id: &str, + org_id: uuid::Uuid, email: Option<&str>, token: Option<&str>, - organization_user_id: Option<&str>, + organization_user_id: Option, ) -> Result< crate::models::PolicyResponseModelListResponseModel, Error, diff --git a/crates/bitwarden-api-api/src/apis/secrets_manager_events_api.rs b/crates/bitwarden-api-api/src/apis/secrets_manager_events_api.rs new file mode 100644 index 000000000..d651ac32f --- /dev/null +++ b/crates/bitwarden-api-api/src/apis/secrets_manager_events_api.rs @@ -0,0 +1,82 @@ +/* + * 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 super::{configuration, Error}; +use crate::apis::ResponseContent; + +/// struct for typed errors of method [`sm_events_service_accounts_service_account_id_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SmEventsServiceAccountsServiceAccountIdGetError { + UnknownValue(serde_json::Value), +} + +pub async fn sm_events_service_accounts_service_account_id_get( + configuration: &configuration::Configuration, + service_account_id: uuid::Uuid, + start: Option, + end: Option, + continuation_token: Option<&str>, +) -> Result< + crate::models::EventResponseModelListResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/sm/events/service-accounts/{serviceAccountId}", + 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_str) = start { + local_var_req_builder = + local_var_req_builder.query(&[("start", &local_var_str.to_string())]); + } + if let Some(ref local_var_str) = end { + local_var_req_builder = local_var_req_builder.query(&[("end", &local_var_str.to_string())]); + } + if let Some(ref local_var_str) = continuation_token { + local_var_req_builder = + local_var_req_builder.query(&[("continuationToken", &local_var_str.to_string())]); + } + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + 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/self_hosted_organization_sponsorships_api.rs b/crates/bitwarden-api-api/src/apis/self_hosted_organization_sponsorships_api.rs index dc2da4103..60b8a6d28 100644 --- a/crates/bitwarden-api-api/src/apis/self_hosted_organization_sponsorships_api.rs +++ b/crates/bitwarden-api-api/src/apis/self_hosted_organization_sponsorships_api.rs @@ -13,21 +13,24 @@ use reqwest; use super::{configuration, Error}; use crate::apis::ResponseContent; -/// struct for typed errors of method [`organization_sponsorship_self_hosted_sponsoring_org_id_delete`] +/// struct for typed errors of method +/// [`organization_sponsorship_self_hosted_sponsoring_org_id_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSelfHostedSponsoringOrgIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_self_hosted_sponsoring_org_id_delete_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_self_hosted_sponsoring_org_id_delete_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSelfHostedSponsoringOrgIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_self_hosted_sponsoring_org_id_families_for_enterprise_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_self_hosted_sponsoring_org_id_families_for_enterprise_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSelfHostedSponsoringOrgIdFamiliesForEnterprisePostError { diff --git a/crates/bitwarden-api-api/src/apis/service_accounts_api.rs b/crates/bitwarden-api-api/src/apis/service_accounts_api.rs index 511fb3674..47045b475 100644 --- a/crates/bitwarden-api-api/src/apis/service_accounts_api.rs +++ b/crates/bitwarden-api-api/src/apis/service_accounts_api.rs @@ -72,8 +72,9 @@ pub enum ServiceAccountsIdPutError { pub async fn organizations_organization_id_service_accounts_get( configuration: &configuration::Configuration, organization_id: uuid::Uuid, + include_access_to_secrets: Option, ) -> Result< - crate::models::ServiceAccountResponseModelListResponseModel, + crate::models::ServiceAccountSecretsDetailsResponseModelListResponseModel, Error, > { let local_var_configuration = configuration; @@ -88,6 +89,10 @@ pub async fn organizations_organization_id_service_accounts_get( let mut local_var_req_builder = local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + if let Some(ref local_var_str) = include_access_to_secrets { + local_var_req_builder = + local_var_req_builder.query(&[("includeAccessToSecrets", &local_var_str.to_string())]); + } if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); diff --git a/crates/bitwarden-api-api/src/apis/web_authn_api.rs b/crates/bitwarden-api-api/src/apis/web_authn_api.rs new file mode 100644 index 000000000..8c269d783 --- /dev/null +++ b/crates/bitwarden-api-api/src/apis/web_authn_api.rs @@ -0,0 +1,324 @@ +/* + * 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 super::{configuration, Error}; +use crate::apis::ResponseContent; + +/// struct for typed errors of method [`webauthn_assertion_options_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WebauthnAssertionOptionsPostError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`webauthn_attestation_options_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WebauthnAttestationOptionsPostError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`webauthn_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WebauthnGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`webauthn_id_delete_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WebauthnIdDeletePostError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`webauthn_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WebauthnPostError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`webauthn_put`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WebauthnPutError { + UnknownValue(serde_json::Value), +} + +pub async fn webauthn_assertion_options_post( + configuration: &configuration::Configuration, + secret_verification_request_model: Option, +) -> Result< + crate::models::WebAuthnLoginAssertionOptionsResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/webauthn/assertion-options", + 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(&secret_verification_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn webauthn_attestation_options_post( + configuration: &configuration::Configuration, + secret_verification_request_model: Option, +) -> Result< + crate::models::WebAuthnCredentialCreateOptionsResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/webauthn/attestation-options", + 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(&secret_verification_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn webauthn_get( + configuration: &configuration::Configuration, +) -> Result> +{ + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/webauthn", 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 webauthn_id_delete_post( + configuration: &configuration::Configuration, + id: uuid::Uuid, + secret_verification_request_model: Option, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/webauthn/{id}/delete", + local_var_configuration.base_path, + id = crate::apis::urlencode(id.to_string()) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + if let Some(ref local_var_token) = local_var_configuration.oauth_access_token { + local_var_req_builder = local_var_req_builder.bearer_auth(local_var_token.to_owned()); + }; + local_var_req_builder = local_var_req_builder.json(&secret_verification_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + 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 webauthn_post( + configuration: &configuration::Configuration, + web_authn_login_credential_create_request_model: Option< + crate::models::WebAuthnLoginCredentialCreateRequestModel, + >, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/webauthn", 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(&web_authn_login_credential_create_request_model); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + 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 webauthn_put( + configuration: &configuration::Configuration, + web_authn_login_credential_update_request_model: Option< + crate::models::WebAuthnLoginCredentialUpdateRequestModel, + >, +) -> Result<(), Error> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/webauthn", 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(&web_authn_login_credential_update_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/models/access_policies_create_request.rs b/crates/bitwarden-api-api/src/models/access_policies_create_request.rs index 0b55b52f1..59a11774e 100644 --- a/crates/bitwarden-api-api/src/models/access_policies_create_request.rs +++ b/crates/bitwarden-api-api/src/models/access_policies_create_request.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccessPoliciesCreateRequest { #[serde( rename = "userAccessPolicyRequests", diff --git a/crates/bitwarden-api-api/src/models/access_policy_request.rs b/crates/bitwarden-api-api/src/models/access_policy_request.rs index 7ec692135..81065c5b5 100644 --- a/crates/bitwarden-api-api/src/models/access_policy_request.rs +++ b/crates/bitwarden-api-api/src/models/access_policy_request.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccessPolicyRequest { #[serde(rename = "granteeId")] pub grantee_id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/models/access_policy_update_request.rs b/crates/bitwarden-api-api/src/models/access_policy_update_request.rs index a783d2a2e..38d7852b0 100644 --- a/crates/bitwarden-api-api/src/models/access_policy_update_request.rs +++ b/crates/bitwarden-api-api/src/models/access_policy_update_request.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccessPolicyUpdateRequest { #[serde(rename = "read")] pub read: bool, diff --git a/crates/bitwarden-api-api/src/models/access_token_create_request_model.rs b/crates/bitwarden-api-api/src/models/access_token_create_request_model.rs index c3c55258b..00a3dfa15 100644 --- a/crates/bitwarden-api-api/src/models/access_token_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/access_token_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccessTokenCreateRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/access_token_creation_response_model.rs b/crates/bitwarden-api-api/src/models/access_token_creation_response_model.rs index 319108e6a..e5b91a94a 100644 --- a/crates/bitwarden-api-api/src/models/access_token_creation_response_model.rs +++ b/crates/bitwarden-api-api/src/models/access_token_creation_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccessTokenCreationResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/access_token_response_model.rs b/crates/bitwarden-api-api/src/models/access_token_response_model.rs index 766dfa42b..eb06b0108 100644 --- a/crates/bitwarden-api-api/src/models/access_token_response_model.rs +++ b/crates/bitwarden-api-api/src/models/access_token_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccessTokenResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/access_token_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/access_token_response_model_list_response_model.rs index eeb65cc65..2b6fa6d82 100644 --- a/crates/bitwarden-api-api/src/models/access_token_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/access_token_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AccessTokenResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/admin_auth_request_update_request_model.rs b/crates/bitwarden-api-api/src/models/admin_auth_request_update_request_model.rs index ba7f199ce..b3629d3a5 100644 --- a/crates/bitwarden-api-api/src/models/admin_auth_request_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/admin_auth_request_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AdminAuthRequestUpdateRequestModel { #[serde(rename = "encryptedUserKey", skip_serializing_if = "Option::is_none")] pub encrypted_user_key: Option, diff --git a/crates/bitwarden-api-api/src/models/algorithm.rs b/crates/bitwarden-api-api/src/models/algorithm.rs new file mode 100644 index 000000000..2c302de0a --- /dev/null +++ b/crates/bitwarden-api-api/src/models/algorithm.rs @@ -0,0 +1,54 @@ +/* + * 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 + */ + +/// +#[repr(i64)] +#[derive( + Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, +)] +pub enum Algorithm { + RS1 = -65535, + RS512 = -259, + RS384 = -258, + RS256 = -257, + ES256K = -47, + PS512 = -39, + PS384 = -38, + PS256 = -37, + ES512 = -36, + ES384 = -35, + EdDSA = -8, + ES256 = -7, +} + +impl ToString for Algorithm { + fn to_string(&self) -> String { + match self { + Self::RS1 => String::from("-65535"), + Self::RS512 => String::from("-259"), + Self::RS384 => String::from("-258"), + Self::RS256 => String::from("-257"), + Self::ES256K => String::from("-47"), + Self::PS512 => String::from("-39"), + Self::PS384 => String::from("-38"), + Self::PS256 => String::from("-37"), + Self::ES512 => String::from("-36"), + Self::ES384 => String::from("-35"), + Self::EdDSA => String::from("-8"), + Self::ES256 => String::from("-7"), + } + } +} + +impl Default for Algorithm { + fn default() -> Algorithm { + Self::RS1 + } +} diff --git a/crates/bitwarden-api-api/src/models/api_key_response_model.rs b/crates/bitwarden-api-api/src/models/api_key_response_model.rs index 5e28ae677..6c7cde826 100644 --- a/crates/bitwarden-api-api/src/models/api_key_response_model.rs +++ b/crates/bitwarden-api-api/src/models/api_key_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ApiKeyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/assertion_options.rs b/crates/bitwarden-api-api/src/models/assertion_options.rs new file mode 100644 index 000000000..a7772e88d --- /dev/null +++ b/crates/bitwarden-api-api/src/models/assertion_options.rs @@ -0,0 +1,44 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AssertionOptions { + #[serde(rename = "status", skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(rename = "errorMessage", skip_serializing_if = "Option::is_none")] + pub error_message: Option, + #[serde(rename = "challenge", skip_serializing_if = "Option::is_none")] + pub challenge: Option, + #[serde(rename = "timeout", skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(rename = "rpId", skip_serializing_if = "Option::is_none")] + pub rp_id: Option, + #[serde(rename = "allowCredentials", skip_serializing_if = "Option::is_none")] + pub allow_credentials: Option>, + #[serde(rename = "userVerification", skip_serializing_if = "Option::is_none")] + pub user_verification: Option, + #[serde(rename = "extensions", skip_serializing_if = "Option::is_none")] + pub extensions: Option>, +} + +impl AssertionOptions { + pub fn new() -> AssertionOptions { + AssertionOptions { + status: None, + error_message: None, + challenge: None, + timeout: None, + rp_id: None, + allow_credentials: None, + user_verification: None, + extensions: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/assertion_response.rs b/crates/bitwarden-api-api/src/models/assertion_response.rs new file mode 100644 index 000000000..bf5f1e792 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/assertion_response.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AssertionResponse { + #[serde(rename = "authenticatorData", skip_serializing_if = "Option::is_none")] + pub authenticator_data: Option, + #[serde(rename = "signature", skip_serializing_if = "Option::is_none")] + pub signature: Option, + #[serde(rename = "clientDataJSON", skip_serializing_if = "Option::is_none")] + pub client_data_json: Option, + #[serde(rename = "userHandle", skip_serializing_if = "Option::is_none")] + pub user_handle: Option, +} + +impl AssertionResponse { + pub fn new() -> AssertionResponse { + AssertionResponse { + authenticator_data: None, + signature: None, + client_data_json: None, + user_handle: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/attachment_request_model.rs b/crates/bitwarden-api-api/src/models/attachment_request_model.rs index 9925ee618..8eeedb507 100644 --- a/crates/bitwarden-api-api/src/models/attachment_request_model.rs +++ b/crates/bitwarden-api-api/src/models/attachment_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AttachmentRequestModel { #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option, diff --git a/crates/bitwarden-api-api/src/models/attachment_response_model.rs b/crates/bitwarden-api-api/src/models/attachment_response_model.rs index 86637a161..e0616818f 100644 --- a/crates/bitwarden-api-api/src/models/attachment_response_model.rs +++ b/crates/bitwarden-api-api/src/models/attachment_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AttachmentResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -21,7 +21,7 @@ pub struct AttachmentResponseModel { #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option, #[serde(rename = "size", skip_serializing_if = "Option::is_none")] - pub size: Option, + pub size: Option, #[serde(rename = "sizeName", skip_serializing_if = "Option::is_none")] pub size_name: Option, } diff --git a/crates/bitwarden-api-api/src/models/attachment_upload_data_response_model.rs b/crates/bitwarden-api-api/src/models/attachment_upload_data_response_model.rs index 4b9f61825..9a78e5807 100644 --- a/crates/bitwarden-api-api/src/models/attachment_upload_data_response_model.rs +++ b/crates/bitwarden-api-api/src/models/attachment_upload_data_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AttachmentUploadDataResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/attestation_conveyance_preference.rs b/crates/bitwarden-api-api/src/models/attestation_conveyance_preference.rs new file mode 100644 index 000000000..0b8a76151 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/attestation_conveyance_preference.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 + */ + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum AttestationConveyancePreference { + #[serde(rename = "none")] + None, + #[serde(rename = "indirect")] + Indirect, + #[serde(rename = "direct")] + Direct, +} + +impl ToString for AttestationConveyancePreference { + fn to_string(&self) -> String { + match self { + Self::None => String::from("none"), + Self::Indirect => String::from("indirect"), + Self::Direct => String::from("direct"), + } + } +} + +impl Default for AttestationConveyancePreference { + fn default() -> AttestationConveyancePreference { + Self::None + } +} diff --git a/crates/bitwarden-api-api/src/models/auth_request_create_request_model.rs b/crates/bitwarden-api-api/src/models/auth_request_create_request_model.rs index 54eee561c..c092ae47d 100644 --- a/crates/bitwarden-api-api/src/models/auth_request_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/auth_request_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AuthRequestCreateRequestModel { #[serde(rename = "email")] pub email: String, diff --git a/crates/bitwarden-api-api/src/models/auth_request_response_model.rs b/crates/bitwarden-api-api/src/models/auth_request_response_model.rs index 8409b4f6c..b6a7438b7 100644 --- a/crates/bitwarden-api-api/src/models/auth_request_response_model.rs +++ b/crates/bitwarden-api-api/src/models/auth_request_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AuthRequestResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/auth_request_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/auth_request_response_model_list_response_model.rs index 70d96a2ed..32e953d28 100644 --- a/crates/bitwarden-api-api/src/models/auth_request_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/auth_request_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AuthRequestResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/auth_request_type.rs b/crates/bitwarden-api-api/src/models/auth_request_type.rs index fbcfb8e9f..1ca3f7790 100644 --- a/crates/bitwarden-api-api/src/models/auth_request_type.rs +++ b/crates/bitwarden-api-api/src/models/auth_request_type.rs @@ -14,23 +14,23 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum AuthRequestType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, + AuthenticateAndUnlock = 0, + Unlock = 1, + AdminApproval = 2, } impl ToString for AuthRequestType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::AuthenticateAndUnlock => String::from("0"), + Self::Unlock => String::from("1"), + Self::AdminApproval => String::from("2"), } } } impl Default for AuthRequestType { fn default() -> AuthRequestType { - Self::Variant0 + Self::AuthenticateAndUnlock } } diff --git a/crates/bitwarden-api-api/src/models/auth_request_update_request_model.rs b/crates/bitwarden-api-api/src/models/auth_request_update_request_model.rs index 6baf69f8a..9799a5fc7 100644 --- a/crates/bitwarden-api-api/src/models/auth_request_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/auth_request_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AuthRequestUpdateRequestModel { #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option, diff --git a/crates/bitwarden-api-api/src/models/authentication_extensions_client_inputs.rs b/crates/bitwarden-api-api/src/models/authentication_extensions_client_inputs.rs new file mode 100644 index 000000000..cd98f01d1 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/authentication_extensions_client_inputs.rs @@ -0,0 +1,35 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AuthenticationExtensionsClientInputs { + #[serde(rename = "example.extension", skip_serializing_if = "Option::is_none")] + pub example_period_extension: Option, + #[serde(rename = "appid", skip_serializing_if = "Option::is_none")] + pub appid: Option, + #[serde(rename = "authnSel", skip_serializing_if = "Option::is_none")] + pub authn_sel: Option>, + #[serde(rename = "exts", skip_serializing_if = "Option::is_none")] + pub exts: Option, + #[serde(rename = "uvm", skip_serializing_if = "Option::is_none")] + pub uvm: Option, +} + +impl AuthenticationExtensionsClientInputs { + pub fn new() -> AuthenticationExtensionsClientInputs { + AuthenticationExtensionsClientInputs { + example_period_extension: None, + appid: None, + authn_sel: None, + exts: None, + uvm: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/authentication_extensions_client_outputs.rs b/crates/bitwarden-api-api/src/models/authentication_extensions_client_outputs.rs index af7deb6da..2af5ed7c5 100644 --- a/crates/bitwarden-api-api/src/models/authentication_extensions_client_outputs.rs +++ b/crates/bitwarden-api-api/src/models/authentication_extensions_client_outputs.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AuthenticationExtensionsClientOutputs { #[serde(rename = "example.extension", skip_serializing_if = "Option::is_none")] pub example_period_extension: Option, diff --git a/crates/bitwarden-api-api/src/models/authenticator_assertion_raw_response.rs b/crates/bitwarden-api-api/src/models/authenticator_assertion_raw_response.rs new file mode 100644 index 000000000..ea39efeab --- /dev/null +++ b/crates/bitwarden-api-api/src/models/authenticator_assertion_raw_response.rs @@ -0,0 +1,35 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AuthenticatorAssertionRawResponse { + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "rawId", skip_serializing_if = "Option::is_none")] + pub raw_id: Option, + #[serde(rename = "response", skip_serializing_if = "Option::is_none")] + pub response: Option>, + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub r#type: Option, + #[serde(rename = "extensions", skip_serializing_if = "Option::is_none")] + pub extensions: Option>, +} + +impl AuthenticatorAssertionRawResponse { + pub fn new() -> AuthenticatorAssertionRawResponse { + AuthenticatorAssertionRawResponse { + id: None, + raw_id: None, + response: None, + r#type: None, + extensions: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/authenticator_attachment.rs b/crates/bitwarden-api-api/src/models/authenticator_attachment.rs new file mode 100644 index 000000000..8e2d2df19 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/authenticator_attachment.rs @@ -0,0 +1,33 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum AuthenticatorAttachment { + #[serde(rename = "platform")] + Platform, + #[serde(rename = "cross-platform")] + CrossPlatform, +} + +impl ToString for AuthenticatorAttachment { + fn to_string(&self) -> String { + match self { + Self::Platform => String::from("platform"), + Self::CrossPlatform => String::from("cross-platform"), + } + } +} + +impl Default for AuthenticatorAttachment { + fn default() -> AuthenticatorAttachment { + Self::Platform + } +} diff --git a/crates/bitwarden-api-api/src/models/authenticator_attestation_raw_response.rs b/crates/bitwarden-api-api/src/models/authenticator_attestation_raw_response.rs index 3e2b4c05c..8763ab261 100644 --- a/crates/bitwarden-api-api/src/models/authenticator_attestation_raw_response.rs +++ b/crates/bitwarden-api-api/src/models/authenticator_attestation_raw_response.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct AuthenticatorAttestationRawResponse { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/authenticator_selection.rs b/crates/bitwarden-api-api/src/models/authenticator_selection.rs new file mode 100644 index 000000000..4159eddde --- /dev/null +++ b/crates/bitwarden-api-api/src/models/authenticator_selection.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AuthenticatorSelection { + #[serde( + rename = "authenticatorAttachment", + skip_serializing_if = "Option::is_none" + )] + pub authenticator_attachment: Option, + #[serde(rename = "requireResidentKey", skip_serializing_if = "Option::is_none")] + pub require_resident_key: Option, + #[serde(rename = "userVerification", skip_serializing_if = "Option::is_none")] + pub user_verification: Option, +} + +impl AuthenticatorSelection { + pub fn new() -> AuthenticatorSelection { + AuthenticatorSelection { + authenticator_attachment: None, + require_resident_key: None, + user_verification: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/authenticator_transport.rs b/crates/bitwarden-api-api/src/models/authenticator_transport.rs new file mode 100644 index 000000000..0773f8138 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/authenticator_transport.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 + */ + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum AuthenticatorTransport { + #[serde(rename = "usb")] + Usb, + #[serde(rename = "nfc")] + Nfc, + #[serde(rename = "ble")] + Ble, + #[serde(rename = "internal")] + Internal, +} + +impl ToString for AuthenticatorTransport { + fn to_string(&self) -> String { + match self { + Self::Usb => String::from("usb"), + Self::Nfc => String::from("nfc"), + Self::Ble => String::from("ble"), + Self::Internal => String::from("internal"), + } + } +} + +impl Default for AuthenticatorTransport { + fn default() -> AuthenticatorTransport { + Self::Usb + } +} diff --git a/crates/bitwarden-api-api/src/models/base_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/base_access_policy_response_model.rs index 39efd3242..cb15dde0b 100644 --- a/crates/bitwarden-api-api/src/models/base_access_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/base_access_policy_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BaseAccessPolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/base_secret_response_model.rs b/crates/bitwarden-api-api/src/models/base_secret_response_model.rs index d2db29bc7..2b486020f 100644 --- a/crates/bitwarden-api-api/src/models/base_secret_response_model.rs +++ b/crates/bitwarden-api-api/src/models/base_secret_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BaseSecretResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/base_secret_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/base_secret_response_model_list_response_model.rs index f65cb215d..650333a80 100644 --- a/crates/bitwarden-api-api/src/models/base_secret_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/base_secret_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BaseSecretResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/billing_customer_discount.rs b/crates/bitwarden-api-api/src/models/billing_customer_discount.rs new file mode 100644 index 000000000..71fc4f46d --- /dev/null +++ b/crates/bitwarden-api-api/src/models/billing_customer_discount.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct BillingCustomerDiscount { + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "active", skip_serializing_if = "Option::is_none")] + pub active: Option, + #[serde(rename = "percentOff", skip_serializing_if = "Option::is_none")] + pub percent_off: Option, + #[serde(rename = "appliesTo", skip_serializing_if = "Option::is_none")] + pub applies_to: Option>, +} + +impl BillingCustomerDiscount { + pub fn new() -> BillingCustomerDiscount { + BillingCustomerDiscount { + id: None, + active: None, + percent_off: None, + applies_to: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/billing_history_response_model.rs b/crates/bitwarden-api-api/src/models/billing_history_response_model.rs index 54f00397f..f1550bcae 100644 --- a/crates/bitwarden-api-api/src/models/billing_history_response_model.rs +++ b/crates/bitwarden-api-api/src/models/billing_history_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingHistoryResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/billing_invoice.rs b/crates/bitwarden-api-api/src/models/billing_invoice.rs index 893930723..23dcb17d2 100644 --- a/crates/bitwarden-api-api/src/models/billing_invoice.rs +++ b/crates/bitwarden-api-api/src/models/billing_invoice.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingInvoice { #[serde(rename = "amount", skip_serializing_if = "Option::is_none")] pub amount: Option, diff --git a/crates/bitwarden-api-api/src/models/billing_payment_response_model.rs b/crates/bitwarden-api-api/src/models/billing_payment_response_model.rs index eb7bc10db..2b1f929a7 100644 --- a/crates/bitwarden-api-api/src/models/billing_payment_response_model.rs +++ b/crates/bitwarden-api-api/src/models/billing_payment_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingPaymentResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: 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 index 7637917e1..4a3cbea6e 100644 --- a/crates/bitwarden-api-api/src/models/billing_response_model.rs +++ b/crates/bitwarden-api-api/src/models/billing_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/billing_source.rs b/crates/bitwarden-api-api/src/models/billing_source.rs index da231d360..cff096377 100644 --- a/crates/bitwarden-api-api/src/models/billing_source.rs +++ b/crates/bitwarden-api-api/src/models/billing_source.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingSource { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, diff --git a/crates/bitwarden-api-api/src/models/billing_subscription.rs b/crates/bitwarden-api-api/src/models/billing_subscription.rs index 0c89977ed..8a0c3aec0 100644 --- a/crates/bitwarden-api-api/src/models/billing_subscription.rs +++ b/crates/bitwarden-api-api/src/models/billing_subscription.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingSubscription { #[serde(rename = "trialStartDate", skip_serializing_if = "Option::is_none")] pub trial_start_date: Option, diff --git a/crates/bitwarden-api-api/src/models/billing_subscription_item.rs b/crates/bitwarden-api-api/src/models/billing_subscription_item.rs index 7a9693eb2..0e40278c5 100644 --- a/crates/bitwarden-api-api/src/models/billing_subscription_item.rs +++ b/crates/bitwarden-api-api/src/models/billing_subscription_item.rs @@ -8,8 +8,10 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingSubscriptionItem { + #[serde(rename = "productId", skip_serializing_if = "Option::is_none")] + pub product_id: Option, #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(rename = "amount", skip_serializing_if = "Option::is_none")] @@ -28,20 +30,18 @@ pub struct BillingSubscriptionItem { skip_serializing_if = "Option::is_none" )] pub addon_subscription_item: Option, - #[serde(rename = "bitwardenProduct", skip_serializing_if = "Option::is_none")] - pub bitwarden_product: Option, } impl BillingSubscriptionItem { pub fn new() -> BillingSubscriptionItem { BillingSubscriptionItem { + product_id: None, name: None, amount: None, quantity: None, interval: None, sponsored_subscription_item: None, addon_subscription_item: None, - bitwarden_product: None, } } } diff --git a/crates/bitwarden-api-api/src/models/billing_subscription_upcoming_invoice.rs b/crates/bitwarden-api-api/src/models/billing_subscription_upcoming_invoice.rs index c4b741a7a..a2e183a94 100644 --- a/crates/bitwarden-api-api/src/models/billing_subscription_upcoming_invoice.rs +++ b/crates/bitwarden-api-api/src/models/billing_subscription_upcoming_invoice.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingSubscriptionUpcomingInvoice { #[serde(rename = "amount", skip_serializing_if = "Option::is_none")] pub amount: Option, diff --git a/crates/bitwarden-api-api/src/models/billing_transaction.rs b/crates/bitwarden-api-api/src/models/billing_transaction.rs index 2343afb8b..666d13919 100644 --- a/crates/bitwarden-api-api/src/models/billing_transaction.rs +++ b/crates/bitwarden-api-api/src/models/billing_transaction.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BillingTransaction { #[serde(rename = "createdDate", skip_serializing_if = "Option::is_none")] pub created_date: Option, 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 c94e9d565..9491aabb0 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BitPayInvoiceRequestModel { #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] pub user_id: Option, diff --git a/crates/bitwarden-api-api/src/models/bulk_collection_access_request_model.rs b/crates/bitwarden-api-api/src/models/bulk_collection_access_request_model.rs new file mode 100644 index 000000000..a7fd75d85 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/bulk_collection_access_request_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct BulkCollectionAccessRequestModel { + #[serde(rename = "collectionIds", skip_serializing_if = "Option::is_none")] + pub collection_ids: Option>, + #[serde(rename = "groups", skip_serializing_if = "Option::is_none")] + pub groups: Option>, + #[serde(rename = "users", skip_serializing_if = "Option::is_none")] + pub users: Option>, +} + +impl BulkCollectionAccessRequestModel { + pub fn new() -> BulkCollectionAccessRequestModel { + BulkCollectionAccessRequestModel { + collection_ids: None, + groups: None, + users: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/bulk_delete_response_model.rs b/crates/bitwarden-api-api/src/models/bulk_delete_response_model.rs index 8bab4b18a..0d9b9e074 100644 --- a/crates/bitwarden-api-api/src/models/bulk_delete_response_model.rs +++ b/crates/bitwarden-api-api/src/models/bulk_delete_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BulkDeleteResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/bulk_delete_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/bulk_delete_response_model_list_response_model.rs index 94d644d0b..800ffeb32 100644 --- a/crates/bitwarden-api-api/src/models/bulk_delete_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/bulk_delete_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BulkDeleteResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/bulk_deny_admin_auth_request_request_model.rs b/crates/bitwarden-api-api/src/models/bulk_deny_admin_auth_request_request_model.rs index 5108fb419..b870cc77b 100644 --- a/crates/bitwarden-api-api/src/models/bulk_deny_admin_auth_request_request_model.rs +++ b/crates/bitwarden-api-api/src/models/bulk_deny_admin_auth_request_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct BulkDenyAdminAuthRequestRequestModel { #[serde(rename = "ids", skip_serializing_if = "Option::is_none")] pub ids: Option>, diff --git a/crates/bitwarden-api-api/src/models/cipher_attachment_model.rs b/crates/bitwarden-api-api/src/models/cipher_attachment_model.rs index 7d55716dd..93beabc99 100644 --- a/crates/bitwarden-api-api/src/models/cipher_attachment_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_attachment_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherAttachmentModel { #[serde(rename = "fileName", skip_serializing_if = "Option::is_none")] pub file_name: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_bulk_delete_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_bulk_delete_request_model.rs index 22202fcaf..b709c5622 100644 --- a/crates/bitwarden-api-api/src/models/cipher_bulk_delete_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_bulk_delete_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherBulkDeleteRequestModel { #[serde(rename = "ids")] pub ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/cipher_bulk_move_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_bulk_move_request_model.rs index eca93c3a7..b9e2b61f5 100644 --- a/crates/bitwarden-api-api/src/models/cipher_bulk_move_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_bulk_move_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherBulkMoveRequestModel { #[serde(rename = "ids")] pub ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/cipher_bulk_restore_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_bulk_restore_request_model.rs index 9fe99c9ce..3d6ad9338 100644 --- a/crates/bitwarden-api-api/src/models/cipher_bulk_restore_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_bulk_restore_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherBulkRestoreRequestModel { #[serde(rename = "ids")] pub ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/cipher_bulk_share_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_bulk_share_request_model.rs index c5d906e0f..92dff066c 100644 --- a/crates/bitwarden-api-api/src/models/cipher_bulk_share_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_bulk_share_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherBulkShareRequestModel { #[serde(rename = "collectionIds")] pub collection_ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/cipher_card_model.rs b/crates/bitwarden-api-api/src/models/cipher_card_model.rs index 173e4e55c..eae29bc84 100644 --- a/crates/bitwarden-api-api/src/models/cipher_card_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_card_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherCardModel { #[serde(rename = "cardholderName", skip_serializing_if = "Option::is_none")] pub cardholder_name: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_collections_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_collections_request_model.rs index cdd2649a9..8bdf4be41 100644 --- a/crates/bitwarden-api-api/src/models/cipher_collections_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_collections_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherCollectionsRequestModel { #[serde(rename = "collectionIds")] pub collection_ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/cipher_create_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_create_request_model.rs index 272343066..bbfdf3c03 100644 --- a/crates/bitwarden-api-api/src/models/cipher_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherCreateRequestModel { #[serde(rename = "collectionIds", skip_serializing_if = "Option::is_none")] pub collection_ids: Option>, diff --git a/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs index 448712702..1dd69ecd9 100644 --- a/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -51,6 +51,8 @@ pub struct CipherDetailsResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "folderId", skip_serializing_if = "Option::is_none")] pub folder_id: Option, #[serde(rename = "favorite", skip_serializing_if = "Option::is_none")] @@ -85,6 +87,7 @@ impl CipherDetailsResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, folder_id: None, favorite: None, edit: None, diff --git a/crates/bitwarden-api-api/src/models/cipher_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_details_response_model_list_response_model.rs index 9e840e978..8da076327 100644 --- a/crates/bitwarden-api-api/src/models/cipher_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_fido2_credential_model.rs b/crates/bitwarden-api-api/src/models/cipher_fido2_credential_model.rs new file mode 100644 index 000000000..95de4bc28 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/cipher_fido2_credential_model.rs @@ -0,0 +1,59 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CipherFido2CredentialModel { + #[serde(rename = "credentialId", skip_serializing_if = "Option::is_none")] + pub credential_id: Option, + #[serde(rename = "keyType", skip_serializing_if = "Option::is_none")] + pub key_type: Option, + #[serde(rename = "keyAlgorithm", skip_serializing_if = "Option::is_none")] + pub key_algorithm: Option, + #[serde(rename = "keyCurve", skip_serializing_if = "Option::is_none")] + pub key_curve: Option, + #[serde(rename = "keyValue", skip_serializing_if = "Option::is_none")] + pub key_value: Option, + #[serde(rename = "rpId", skip_serializing_if = "Option::is_none")] + pub rp_id: Option, + #[serde(rename = "rpName", skip_serializing_if = "Option::is_none")] + pub rp_name: Option, + #[serde(rename = "userHandle", skip_serializing_if = "Option::is_none")] + pub user_handle: Option, + #[serde(rename = "userName", skip_serializing_if = "Option::is_none")] + pub user_name: Option, + #[serde(rename = "userDisplayName", skip_serializing_if = "Option::is_none")] + pub user_display_name: Option, + #[serde(rename = "counter", skip_serializing_if = "Option::is_none")] + pub counter: Option, + #[serde(rename = "discoverable", skip_serializing_if = "Option::is_none")] + pub discoverable: Option, + #[serde(rename = "creationDate")] + pub creation_date: String, +} + +impl CipherFido2CredentialModel { + pub fn new(creation_date: String) -> CipherFido2CredentialModel { + CipherFido2CredentialModel { + credential_id: None, + key_type: None, + key_algorithm: None, + key_curve: None, + key_value: None, + rp_id: None, + rp_name: None, + user_handle: None, + user_name: None, + user_display_name: None, + counter: None, + discoverable: None, + creation_date, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/cipher_field_model.rs b/crates/bitwarden-api-api/src/models/cipher_field_model.rs index 937e52e33..3b1700dd6 100644 --- a/crates/bitwarden-api-api/src/models/cipher_field_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_field_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherFieldModel { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_identity_model.rs b/crates/bitwarden-api-api/src/models/cipher_identity_model.rs index 54463ad3a..e2e629398 100644 --- a/crates/bitwarden-api-api/src/models/cipher_identity_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_identity_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherIdentityModel { #[serde(rename = "title", skip_serializing_if = "Option::is_none")] pub title: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_login_model.rs b/crates/bitwarden-api-api/src/models/cipher_login_model.rs index d9f531154..159bee7cd 100644 --- a/crates/bitwarden-api-api/src/models/cipher_login_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_login_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherLoginModel { #[serde(rename = "uri", skip_serializing_if = "Option::is_none")] pub uri: Option, @@ -27,6 +27,8 @@ pub struct CipherLoginModel { pub totp: Option, #[serde(rename = "autofillOnPageLoad", skip_serializing_if = "Option::is_none")] pub autofill_on_page_load: Option, + #[serde(rename = "fido2Credentials", skip_serializing_if = "Option::is_none")] + pub fido2_credentials: Option>, } impl CipherLoginModel { @@ -39,6 +41,7 @@ impl CipherLoginModel { password_revision_date: None, totp: None, autofill_on_page_load: None, + fido2_credentials: None, } } } diff --git a/crates/bitwarden-api-api/src/models/cipher_login_uri_model.rs b/crates/bitwarden-api-api/src/models/cipher_login_uri_model.rs index 07358be79..39b6139c7 100644 --- a/crates/bitwarden-api-api/src/models/cipher_login_uri_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_login_uri_model.rs @@ -8,10 +8,12 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherLoginUriModel { #[serde(rename = "uri", skip_serializing_if = "Option::is_none")] pub uri: Option, + #[serde(rename = "uriChecksum", skip_serializing_if = "Option::is_none")] + pub uri_checksum: Option, #[serde(rename = "match", skip_serializing_if = "Option::is_none")] pub r#match: Option, } @@ -20,6 +22,7 @@ impl CipherLoginUriModel { pub fn new() -> CipherLoginUriModel { CipherLoginUriModel { uri: None, + uri_checksum: None, r#match: None, } } diff --git a/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs index 28dafcdbb..bf8e29707 100644 --- a/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherMiniDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -51,6 +51,8 @@ pub struct CipherMiniDetailsResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "collectionIds", skip_serializing_if = "Option::is_none")] pub collection_ids: Option>, } @@ -77,6 +79,7 @@ impl CipherMiniDetailsResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, collection_ids: None, } } diff --git a/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model_list_response_model.rs index f0c30765f..8fbdaa67c 100644 --- a/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_mini_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherMiniDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs index 8ea52926d..b6e77e2a2 100644 --- a/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_mini_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherMiniResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -51,6 +51,8 @@ pub struct CipherMiniResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, } impl CipherMiniResponseModel { @@ -75,6 +77,7 @@ impl CipherMiniResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, } } } diff --git a/crates/bitwarden-api-api/src/models/cipher_mini_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_mini_response_model_list_response_model.rs index 10dbe91ec..ccae92e9e 100644 --- a/crates/bitwarden-api-api/src/models/cipher_mini_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_mini_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherMiniResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_partial_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_partial_request_model.rs index 791dc52d1..f911ef5cb 100644 --- a/crates/bitwarden-api-api/src/models/cipher_partial_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_partial_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherPartialRequestModel { #[serde(rename = "folderId", skip_serializing_if = "Option::is_none")] pub folder_id: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_password_history_model.rs b/crates/bitwarden-api-api/src/models/cipher_password_history_model.rs index b7ea59de0..4ab05138a 100644 --- a/crates/bitwarden-api-api/src/models/cipher_password_history_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_password_history_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherPasswordHistoryModel { #[serde(rename = "password")] pub password: String, diff --git a/crates/bitwarden-api-api/src/models/cipher_reprompt_type.rs b/crates/bitwarden-api-api/src/models/cipher_reprompt_type.rs index d20cef2db..4443b8c03 100644 --- a/crates/bitwarden-api-api/src/models/cipher_reprompt_type.rs +++ b/crates/bitwarden-api-api/src/models/cipher_reprompt_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum CipherRepromptType { - Variant0 = 0, - Variant1 = 1, + None = 0, + Password = 1, } impl ToString for CipherRepromptType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::None => String::from("0"), + Self::Password => String::from("1"), } } } impl Default for CipherRepromptType { fn default() -> CipherRepromptType { - Self::Variant0 + Self::None } } diff --git a/crates/bitwarden-api-api/src/models/cipher_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_request_model.rs index 4ac3f62d7..8f8d17312 100644 --- a/crates/bitwarden-api-api/src/models/cipher_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherRequestModel { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, @@ -20,6 +20,8 @@ pub struct CipherRequestModel { pub favorite: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "name")] pub name: String, #[serde(rename = "notes", skip_serializing_if = "Option::is_none")] @@ -56,6 +58,7 @@ impl CipherRequestModel { folder_id: None, favorite: None, reprompt: None, + key: None, name, notes: None, fields: None, diff --git a/crates/bitwarden-api-api/src/models/cipher_response_model.rs b/crates/bitwarden-api-api/src/models/cipher_response_model.rs index 62a930e1f..bd9c360b3 100644 --- a/crates/bitwarden-api-api/src/models/cipher_response_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -51,6 +51,8 @@ pub struct CipherResponseModel { pub deleted_date: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "folderId", skip_serializing_if = "Option::is_none")] pub folder_id: Option, #[serde(rename = "favorite", skip_serializing_if = "Option::is_none")] @@ -83,6 +85,7 @@ impl CipherResponseModel { creation_date: None, deleted_date: None, reprompt: None, + key: None, folder_id: None, favorite: None, edit: None, diff --git a/crates/bitwarden-api-api/src/models/cipher_secure_note_model.rs b/crates/bitwarden-api-api/src/models/cipher_secure_note_model.rs index 6cb7a0a2a..a1cb38421 100644 --- a/crates/bitwarden-api-api/src/models/cipher_secure_note_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_secure_note_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherSecureNoteModel { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, diff --git a/crates/bitwarden-api-api/src/models/cipher_share_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_share_request_model.rs index ec5cdab25..86dcb3454 100644 --- a/crates/bitwarden-api-api/src/models/cipher_share_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_share_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherShareRequestModel { #[serde(rename = "collectionIds")] pub collection_ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/cipher_type.rs b/crates/bitwarden-api-api/src/models/cipher_type.rs index 1420ac6a4..044349ef8 100644 --- a/crates/bitwarden-api-api/src/models/cipher_type.rs +++ b/crates/bitwarden-api-api/src/models/cipher_type.rs @@ -14,25 +14,25 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum CipherType { - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, + Login = 1, + SecureNote = 2, + Card = 3, + Identity = 4, } impl ToString for CipherType { fn to_string(&self) -> String { match self { - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), + Self::Login => String::from("1"), + Self::SecureNote => String::from("2"), + Self::Card => String::from("3"), + Self::Identity => String::from("4"), } } } impl Default for CipherType { fn default() -> CipherType { - Self::Variant1 + Self::Login } } diff --git a/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs b/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs index db4c3604f..665bd4b4d 100644 --- a/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs +++ b/crates/bitwarden-api-api/src/models/cipher_with_id_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CipherWithIdRequestModel { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, @@ -20,6 +20,8 @@ pub struct CipherWithIdRequestModel { pub favorite: Option, #[serde(rename = "reprompt", skip_serializing_if = "Option::is_none")] pub reprompt: Option, + #[serde(rename = "key", skip_serializing_if = "Option::is_none")] + pub key: Option, #[serde(rename = "name")] pub name: String, #[serde(rename = "notes", skip_serializing_if = "Option::is_none")] @@ -58,6 +60,7 @@ impl CipherWithIdRequestModel { folder_id: None, favorite: None, reprompt: None, + key: None, name, notes: None, fields: None, diff --git a/crates/bitwarden-api-api/src/models/collection_access_details_response_model.rs b/crates/bitwarden-api-api/src/models/collection_access_details_response_model.rs index faaf5b47a..99ba1ed9b 100644 --- a/crates/bitwarden-api-api/src/models/collection_access_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_access_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionAccessDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/collection_access_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/collection_access_details_response_model_list_response_model.rs index 21d388fdf..89b0cb89a 100644 --- a/crates/bitwarden-api-api/src/models/collection_access_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_access_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionAccessDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/collection_bulk_delete_request_model.rs b/crates/bitwarden-api-api/src/models/collection_bulk_delete_request_model.rs index 7d2d5d8fe..d919294b9 100644 --- a/crates/bitwarden-api-api/src/models/collection_bulk_delete_request_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_bulk_delete_request_model.rs @@ -8,19 +8,14 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionBulkDeleteRequestModel { #[serde(rename = "ids")] - pub ids: Vec, - #[serde(rename = "organizationId", skip_serializing_if = "Option::is_none")] - pub organization_id: Option, + pub ids: Vec, } impl CollectionBulkDeleteRequestModel { - pub fn new(ids: Vec) -> CollectionBulkDeleteRequestModel { - CollectionBulkDeleteRequestModel { - ids, - organization_id: None, - } + pub fn new(ids: Vec) -> CollectionBulkDeleteRequestModel { + CollectionBulkDeleteRequestModel { ids } } } diff --git a/crates/bitwarden-api-api/src/models/collection_details_response_model.rs b/crates/bitwarden-api-api/src/models/collection_details_response_model.rs index f364a98e8..3275bb104 100644 --- a/crates/bitwarden-api-api/src/models/collection_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -24,6 +24,8 @@ pub struct CollectionDetailsResponseModel { pub read_only: Option, #[serde(rename = "hidePasswords", skip_serializing_if = "Option::is_none")] pub hide_passwords: Option, + #[serde(rename = "manage", skip_serializing_if = "Option::is_none")] + pub manage: Option, } impl CollectionDetailsResponseModel { @@ -36,6 +38,7 @@ impl CollectionDetailsResponseModel { external_id: None, read_only: None, hide_passwords: None, + manage: None, } } } diff --git a/crates/bitwarden-api-api/src/models/collection_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/collection_details_response_model_list_response_model.rs index 8e71cb637..e505be1dd 100644 --- a/crates/bitwarden-api-api/src/models/collection_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/collection_request_model.rs b/crates/bitwarden-api-api/src/models/collection_request_model.rs index 3b3449d8f..6a67c959c 100644 --- a/crates/bitwarden-api-api/src/models/collection_request_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/collection_response_model.rs b/crates/bitwarden-api-api/src/models/collection_response_model.rs index 580adc1bb..091a88fea 100644 --- a/crates/bitwarden-api-api/src/models/collection_response_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/collection_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/collection_response_model_list_response_model.rs index 6a5e3720d..943be4ca5 100644 --- a/crates/bitwarden-api-api/src/models/collection_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/collection_with_id_request_model.rs b/crates/bitwarden-api-api/src/models/collection_with_id_request_model.rs index 659dbf617..ae621c621 100644 --- a/crates/bitwarden-api-api/src/models/collection_with_id_request_model.rs +++ b/crates/bitwarden-api-api/src/models/collection_with_id_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CollectionWithIdRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/config_response_model.rs b/crates/bitwarden-api-api/src/models/config_response_model.rs index 6ac6d935c..3d087338f 100644 --- a/crates/bitwarden-api-api/src/models/config_response_model.rs +++ b/crates/bitwarden-api-api/src/models/config_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ConfigResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/credential_create_options.rs b/crates/bitwarden-api-api/src/models/credential_create_options.rs new file mode 100644 index 000000000..57d6799f9 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/credential_create_options.rs @@ -0,0 +1,56 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CredentialCreateOptions { + #[serde(rename = "status", skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(rename = "errorMessage", skip_serializing_if = "Option::is_none")] + pub error_message: Option, + #[serde(rename = "rp", skip_serializing_if = "Option::is_none")] + pub rp: Option>, + #[serde(rename = "user", skip_serializing_if = "Option::is_none")] + pub user: Option>, + #[serde(rename = "challenge", skip_serializing_if = "Option::is_none")] + pub challenge: Option, + #[serde(rename = "pubKeyCredParams", skip_serializing_if = "Option::is_none")] + pub pub_key_cred_params: Option>, + #[serde(rename = "timeout", skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(rename = "attestation", skip_serializing_if = "Option::is_none")] + pub attestation: Option, + #[serde( + rename = "authenticatorSelection", + skip_serializing_if = "Option::is_none" + )] + pub authenticator_selection: Option>, + #[serde(rename = "excludeCredentials", skip_serializing_if = "Option::is_none")] + pub exclude_credentials: Option>, + #[serde(rename = "extensions", skip_serializing_if = "Option::is_none")] + pub extensions: Option>, +} + +impl CredentialCreateOptions { + pub fn new() -> CredentialCreateOptions { + CredentialCreateOptions { + status: None, + error_message: None, + rp: None, + user: None, + challenge: None, + pub_key_cred_params: None, + timeout: None, + attestation: None, + authenticator_selection: None, + exclude_credentials: None, + extensions: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/delete_recover_request_model.rs b/crates/bitwarden-api-api/src/models/delete_recover_request_model.rs index 4a4026d98..a065a1724 100644 --- a/crates/bitwarden-api-api/src/models/delete_recover_request_model.rs +++ b/crates/bitwarden-api-api/src/models/delete_recover_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeleteRecoverRequestModel { #[serde(rename = "email")] pub email: String, diff --git a/crates/bitwarden-api-api/src/models/device_keys_request_model.rs b/crates/bitwarden-api-api/src/models/device_keys_request_model.rs index 4b6fdbb69..4864103b8 100644 --- a/crates/bitwarden-api-api/src/models/device_keys_request_model.rs +++ b/crates/bitwarden-api-api/src/models/device_keys_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeviceKeysRequestModel { #[serde(rename = "encryptedUserKey")] pub encrypted_user_key: String, diff --git a/crates/bitwarden-api-api/src/models/device_keys_update_request_model.rs b/crates/bitwarden-api-api/src/models/device_keys_update_request_model.rs new file mode 100644 index 000000000..8000d96fe --- /dev/null +++ b/crates/bitwarden-api-api/src/models/device_keys_update_request_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DeviceKeysUpdateRequestModel { + #[serde(rename = "encryptedPublicKey")] + pub encrypted_public_key: String, + #[serde(rename = "encryptedUserKey")] + pub encrypted_user_key: String, +} + +impl DeviceKeysUpdateRequestModel { + pub fn new( + encrypted_public_key: String, + encrypted_user_key: String, + ) -> DeviceKeysUpdateRequestModel { + DeviceKeysUpdateRequestModel { + encrypted_public_key, + encrypted_user_key, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/device_request_model.rs b/crates/bitwarden-api-api/src/models/device_request_model.rs index 556350265..856801792 100644 --- a/crates/bitwarden-api-api/src/models/device_request_model.rs +++ b/crates/bitwarden-api-api/src/models/device_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeviceRequestModel { #[serde(rename = "type")] pub r#type: crate::models::DeviceType, diff --git a/crates/bitwarden-api-api/src/models/device_response_model.rs b/crates/bitwarden-api-api/src/models/device_response_model.rs index 463d6f104..08a7bfe2f 100644 --- a/crates/bitwarden-api-api/src/models/device_response_model.rs +++ b/crates/bitwarden-api-api/src/models/device_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeviceResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -22,15 +22,8 @@ pub struct DeviceResponseModel { pub identifier: Option, #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] pub creation_date: Option, - #[serde(rename = "encryptedUserKey", skip_serializing_if = "Option::is_none")] - pub encrypted_user_key: Option, - #[serde(rename = "encryptedPublicKey", skip_serializing_if = "Option::is_none")] - pub encrypted_public_key: Option, - #[serde( - rename = "encryptedPrivateKey", - skip_serializing_if = "Option::is_none" - )] - pub encrypted_private_key: Option, + #[serde(rename = "isTrusted", skip_serializing_if = "Option::is_none")] + pub is_trusted: Option, } impl DeviceResponseModel { @@ -42,9 +35,7 @@ impl DeviceResponseModel { r#type: None, identifier: None, creation_date: None, - encrypted_user_key: None, - encrypted_public_key: None, - encrypted_private_key: None, + is_trusted: None, } } } diff --git a/crates/bitwarden-api-api/src/models/device_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/device_response_model_list_response_model.rs index 47ab93fae..6db95f61f 100644 --- a/crates/bitwarden-api-api/src/models/device_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/device_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeviceResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/device_token_request_model.rs b/crates/bitwarden-api-api/src/models/device_token_request_model.rs index 1e0b8b3b0..295d8545d 100644 --- a/crates/bitwarden-api-api/src/models/device_token_request_model.rs +++ b/crates/bitwarden-api-api/src/models/device_token_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeviceTokenRequestModel { #[serde(rename = "pushToken", skip_serializing_if = "Option::is_none")] pub push_token: Option, diff --git a/crates/bitwarden-api-api/src/models/device_type.rs b/crates/bitwarden-api-api/src/models/device_type.rs index b6785618a..12e162ce6 100644 --- a/crates/bitwarden-api-api/src/models/device_type.rs +++ b/crates/bitwarden-api-api/src/models/device_type.rs @@ -14,63 +14,69 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum DeviceType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, - Variant8 = 8, - Variant9 = 9, - Variant10 = 10, - Variant11 = 11, - Variant12 = 12, - Variant13 = 13, - Variant14 = 14, - Variant15 = 15, - Variant16 = 16, - Variant17 = 17, - Variant18 = 18, - Variant19 = 19, - Variant20 = 20, - Variant21 = 21, - Variant22 = 22, + Android = 0, + iOS = 1, + ChromeExtension = 2, + FirefoxExtension = 3, + OperaExtension = 4, + EdgeExtension = 5, + WindowsDesktop = 6, + MacOsDesktop = 7, + LinuxDesktop = 8, + ChromeBrowser = 9, + FirefoxBrowser = 10, + OperaBrowser = 11, + EdgeBrowser = 12, + IEBrowser = 13, + UnknownBrowser = 14, + AndroidAmazon = 15, + UWP = 16, + SafariBrowser = 17, + VivaldiBrowser = 18, + VivaldiExtension = 19, + SafariExtension = 20, + SDK = 21, + Server = 22, + WindowsCLI = 23, + MacOsCLI = 24, + LinuxCLI = 25, } impl ToString for DeviceType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), - Self::Variant8 => String::from("8"), - Self::Variant9 => String::from("9"), - Self::Variant10 => String::from("10"), - Self::Variant11 => String::from("11"), - Self::Variant12 => String::from("12"), - Self::Variant13 => String::from("13"), - Self::Variant14 => String::from("14"), - Self::Variant15 => String::from("15"), - Self::Variant16 => String::from("16"), - Self::Variant17 => String::from("17"), - Self::Variant18 => String::from("18"), - Self::Variant19 => String::from("19"), - Self::Variant20 => String::from("20"), - Self::Variant21 => String::from("21"), - Self::Variant22 => String::from("22"), + Self::Android => String::from("0"), + Self::iOS => String::from("1"), + Self::ChromeExtension => String::from("2"), + Self::FirefoxExtension => String::from("3"), + Self::OperaExtension => String::from("4"), + Self::EdgeExtension => String::from("5"), + Self::WindowsDesktop => String::from("6"), + Self::MacOsDesktop => String::from("7"), + Self::LinuxDesktop => String::from("8"), + Self::ChromeBrowser => String::from("9"), + Self::FirefoxBrowser => String::from("10"), + Self::OperaBrowser => String::from("11"), + Self::EdgeBrowser => String::from("12"), + Self::IEBrowser => String::from("13"), + Self::UnknownBrowser => String::from("14"), + Self::AndroidAmazon => String::from("15"), + Self::UWP => String::from("16"), + Self::SafariBrowser => String::from("17"), + Self::VivaldiBrowser => String::from("18"), + Self::VivaldiExtension => String::from("19"), + Self::SafariExtension => String::from("20"), + Self::SDK => String::from("21"), + Self::Server => String::from("22"), + Self::WindowsCLI => String::from("23"), + Self::MacOsCLI => String::from("24"), + Self::LinuxCLI => String::from("25"), } } } impl Default for DeviceType { fn default() -> DeviceType { - Self::Variant0 + Self::Android } } diff --git a/crates/bitwarden-api-api/src/models/device_verification_request_model.rs b/crates/bitwarden-api-api/src/models/device_verification_request_model.rs index bba9b491a..e6a6907f5 100644 --- a/crates/bitwarden-api-api/src/models/device_verification_request_model.rs +++ b/crates/bitwarden-api-api/src/models/device_verification_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeviceVerificationRequestModel { #[serde(rename = "unknownDeviceVerificationEnabled")] pub unknown_device_verification_enabled: bool, diff --git a/crates/bitwarden-api-api/src/models/device_verification_response_model.rs b/crates/bitwarden-api-api/src/models/device_verification_response_model.rs index e969a8d5b..e9077828b 100644 --- a/crates/bitwarden-api-api/src/models/device_verification_response_model.rs +++ b/crates/bitwarden-api-api/src/models/device_verification_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DeviceVerificationResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/domains_response_model.rs b/crates/bitwarden-api-api/src/models/domains_response_model.rs index 3e4cc73db..8ccab95c1 100644 --- a/crates/bitwarden-api-api/src/models/domains_response_model.rs +++ b/crates/bitwarden-api-api/src/models/domains_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DomainsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/email_request_model.rs b/crates/bitwarden-api-api/src/models/email_request_model.rs index 24a205f63..e8db8de94 100644 --- a/crates/bitwarden-api-api/src/models/email_request_model.rs +++ b/crates/bitwarden-api-api/src/models/email_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmailRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/email_token_request_model.rs b/crates/bitwarden-api-api/src/models/email_token_request_model.rs index ed29f776c..09ea36974 100644 --- a/crates/bitwarden-api-api/src/models/email_token_request_model.rs +++ b/crates/bitwarden-api-api/src/models/email_token_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmailTokenRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model.rs index d6b7945e6..6869f6709 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessGranteeDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model_list_response_model.rs index 1fc800217..9d76aaea9 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_grantee_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessGranteeDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model.rs index ecf1eb186..902cd938c 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessGrantorDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model_list_response_model.rs index 51cbabb3e..414f5037f 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_grantor_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessGrantorDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_invite_request_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_invite_request_model.rs index 3ec945bc6..2d85903aa 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_invite_request_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_invite_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessInviteRequestModel { #[serde(rename = "email")] pub email: String, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_password_request_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_password_request_model.rs index 6687a57fe..091a31495 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_password_request_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_password_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessPasswordRequestModel { #[serde(rename = "newMasterPasswordHash")] pub new_master_password_hash: String, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_status_type.rs b/crates/bitwarden-api-api/src/models/emergency_access_status_type.rs index a4766cec4..9b046f3eb 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_status_type.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_status_type.rs @@ -14,27 +14,27 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum EmergencyAccessStatusType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, + Invited = 0, + Accepted = 1, + Confirmed = 2, + RecoveryInitiated = 3, + RecoveryApproved = 4, } impl ToString for EmergencyAccessStatusType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), + Self::Invited => String::from("0"), + Self::Accepted => String::from("1"), + Self::Confirmed => String::from("2"), + Self::RecoveryInitiated => String::from("3"), + Self::RecoveryApproved => String::from("4"), } } } impl Default for EmergencyAccessStatusType { fn default() -> EmergencyAccessStatusType { - Self::Variant0 + Self::Invited } } diff --git a/crates/bitwarden-api-api/src/models/emergency_access_takeover_response_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_takeover_response_model.rs index ce22542ea..c4bd0786a 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_takeover_response_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_takeover_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessTakeoverResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_type.rs b/crates/bitwarden-api-api/src/models/emergency_access_type.rs index 007df25a2..317c42f94 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_type.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum EmergencyAccessType { - Variant0 = 0, - Variant1 = 1, + View = 0, + Takeover = 1, } impl ToString for EmergencyAccessType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::View => String::from("0"), + Self::Takeover => String::from("1"), } } } impl Default for EmergencyAccessType { fn default() -> EmergencyAccessType { - Self::Variant0 + Self::View } } diff --git a/crates/bitwarden-api-api/src/models/emergency_access_update_request_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_update_request_model.rs index 184e4288b..433041c35 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessUpdateRequestModel { #[serde(rename = "type")] pub r#type: crate::models::EmergencyAccessType, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_view_response_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_view_response_model.rs index 066162dc1..4e58cda5e 100644 --- a/crates/bitwarden-api-api/src/models/emergency_access_view_response_model.rs +++ b/crates/bitwarden-api-api/src/models/emergency_access_view_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmergencyAccessViewResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/emergency_access_with_id_request_model.rs b/crates/bitwarden-api-api/src/models/emergency_access_with_id_request_model.rs new file mode 100644 index 000000000..45d16e39b --- /dev/null +++ b/crates/bitwarden-api-api/src/models/emergency_access_with_id_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 + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct EmergencyAccessWithIdRequestModel { + #[serde(rename = "type")] + pub r#type: crate::models::EmergencyAccessType, + #[serde(rename = "waitTimeDays")] + pub wait_time_days: i32, + #[serde(rename = "keyEncrypted", skip_serializing_if = "Option::is_none")] + pub key_encrypted: Option, + #[serde(rename = "id")] + pub id: uuid::Uuid, +} + +impl EmergencyAccessWithIdRequestModel { + pub fn new( + r#type: crate::models::EmergencyAccessType, + wait_time_days: i32, + id: uuid::Uuid, + ) -> EmergencyAccessWithIdRequestModel { + EmergencyAccessWithIdRequestModel { + r#type, + wait_time_days, + key_encrypted: None, + id, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/environment_config_response_model.rs b/crates/bitwarden-api-api/src/models/environment_config_response_model.rs index 1cceee850..9bb550571 100644 --- a/crates/bitwarden-api-api/src/models/environment_config_response_model.rs +++ b/crates/bitwarden-api-api/src/models/environment_config_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EnvironmentConfigResponseModel { #[serde(rename = "cloudRegion", skip_serializing_if = "Option::is_none")] pub cloud_region: Option, diff --git a/crates/bitwarden-api-api/src/models/event_response_model.rs b/crates/bitwarden-api-api/src/models/event_response_model.rs index 03ce76ed7..b634ef7dc 100644 --- a/crates/bitwarden-api-api/src/models/event_response_model.rs +++ b/crates/bitwarden-api-api/src/models/event_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EventResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/event_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/event_response_model_list_response_model.rs index e2042731c..40f1d9e8e 100644 --- a/crates/bitwarden-api-api/src/models/event_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/event_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EventResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/event_system_user.rs b/crates/bitwarden-api-api/src/models/event_system_user.rs index 8a1795433..afb1333e8 100644 --- a/crates/bitwarden-api-api/src/models/event_system_user.rs +++ b/crates/bitwarden-api-api/src/models/event_system_user.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum EventSystemUser { - Variant1 = 1, - Variant2 = 2, + SCIM = 1, + DomainVerification = 2, } impl ToString for EventSystemUser { fn to_string(&self) -> String { match self { - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::SCIM => String::from("1"), + Self::DomainVerification => String::from("2"), } } } impl Default for EventSystemUser { fn default() -> EventSystemUser { - Self::Variant1 + Self::SCIM } } diff --git a/crates/bitwarden-api-api/src/models/event_type.rs b/crates/bitwarden-api-api/src/models/event_type.rs index 3988c656f..2826c1fb4 100644 --- a/crates/bitwarden-api-api/src/models/event_type.rs +++ b/crates/bitwarden-api-api/src/models/event_type.rs @@ -14,157 +14,165 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum EventType { - Variant1000 = 1000, - Variant1001 = 1001, - Variant1002 = 1002, - Variant1003 = 1003, - Variant1004 = 1004, - Variant1005 = 1005, - Variant1006 = 1006, - Variant1007 = 1007, - Variant1008 = 1008, - Variant1009 = 1009, - Variant1100 = 1100, - Variant1101 = 1101, - Variant1102 = 1102, - Variant1103 = 1103, - Variant1104 = 1104, - Variant1105 = 1105, - Variant1106 = 1106, - Variant1107 = 1107, - Variant1108 = 1108, - Variant1109 = 1109, - Variant1110 = 1110, - Variant1111 = 1111, - Variant1112 = 1112, - Variant1113 = 1113, - Variant1114 = 1114, - Variant1115 = 1115, - Variant1116 = 1116, - Variant1117 = 1117, - Variant1300 = 1300, - Variant1301 = 1301, - Variant1302 = 1302, - Variant1400 = 1400, - Variant1401 = 1401, - Variant1402 = 1402, - Variant1500 = 1500, - Variant1501 = 1501, - Variant1502 = 1502, - Variant1503 = 1503, - Variant1504 = 1504, - Variant1505 = 1505, - Variant1506 = 1506, - Variant1507 = 1507, - Variant1508 = 1508, - Variant1509 = 1509, - Variant1510 = 1510, - Variant1511 = 1511, - Variant1512 = 1512, - Variant1600 = 1600, - Variant1601 = 1601, - Variant1602 = 1602, - Variant1603 = 1603, - Variant1604 = 1604, - Variant1605 = 1605, - Variant1606 = 1606, - Variant1607 = 1607, - Variant1608 = 1608, - Variant1700 = 1700, - Variant1800 = 1800, - Variant1801 = 1801, - Variant1802 = 1802, - Variant1803 = 1803, - Variant1900 = 1900, - Variant1901 = 1901, - Variant1902 = 1902, - Variant1903 = 1903, - Variant2000 = 2000, - Variant2001 = 2001, - Variant2002 = 2002, - Variant2003 = 2003, - Variant2100 = 2100, + User_LoggedIn = 1000, + User_ChangedPassword = 1001, + User_Updated2fa = 1002, + User_Disabled2fa = 1003, + User_Recovered2fa = 1004, + User_FailedLogIn = 1005, + User_FailedLogIn2fa = 1006, + User_ClientExportedVault = 1007, + User_UpdatedTempPassword = 1008, + User_MigratedKeyToKeyConnector = 1009, + User_RequestedDeviceApproval = 1010, + Cipher_Created = 1100, + Cipher_Updated = 1101, + Cipher_Deleted = 1102, + Cipher_AttachmentCreated = 1103, + Cipher_AttachmentDeleted = 1104, + Cipher_Shared = 1105, + Cipher_UpdatedCollections = 1106, + Cipher_ClientViewed = 1107, + Cipher_ClientToggledPasswordVisible = 1108, + Cipher_ClientToggledHiddenFieldVisible = 1109, + Cipher_ClientToggledCardCodeVisible = 1110, + Cipher_ClientCopiedPassword = 1111, + Cipher_ClientCopiedHiddenField = 1112, + Cipher_ClientCopiedCardCode = 1113, + Cipher_ClientAutofilled = 1114, + Cipher_SoftDeleted = 1115, + Cipher_Restored = 1116, + Cipher_ClientToggledCardNumberVisible = 1117, + Collection_Created = 1300, + Collection_Updated = 1301, + Collection_Deleted = 1302, + Group_Created = 1400, + Group_Updated = 1401, + Group_Deleted = 1402, + OrganizationUser_Invited = 1500, + OrganizationUser_Confirmed = 1501, + OrganizationUser_Updated = 1502, + OrganizationUser_Removed = 1503, + OrganizationUser_UpdatedGroups = 1504, + OrganizationUser_UnlinkedSso = 1505, + OrganizationUser_ResetPassword_Enroll = 1506, + OrganizationUser_ResetPassword_Withdraw = 1507, + OrganizationUser_AdminResetPassword = 1508, + OrganizationUser_ResetSsoLink = 1509, + OrganizationUser_FirstSsoLogin = 1510, + OrganizationUser_Revoked = 1511, + OrganizationUser_Restored = 1512, + OrganizationUser_ApprovedAuthRequest = 1513, + OrganizationUser_RejectedAuthRequest = 1514, + Organization_Updated = 1600, + Organization_PurgedVault = 1601, + Organization_ClientExportedVault = 1602, + Organization_VaultAccessed = 1603, + Organization_EnabledSso = 1604, + Organization_DisabledSso = 1605, + Organization_EnabledKeyConnector = 1606, + Organization_DisabledKeyConnector = 1607, + Organization_SponsorshipsSynced = 1608, + Organization_CollectionManagement_Updated = 1609, + Policy_Updated = 1700, + ProviderUser_Invited = 1800, + ProviderUser_Confirmed = 1801, + ProviderUser_Updated = 1802, + ProviderUser_Removed = 1803, + ProviderOrganization_Created = 1900, + ProviderOrganization_Added = 1901, + ProviderOrganization_Removed = 1902, + ProviderOrganization_VaultAccessed = 1903, + OrganizationDomain_Added = 2000, + OrganizationDomain_Removed = 2001, + OrganizationDomain_Verified = 2002, + OrganizationDomain_NotVerified = 2003, + Secret_Retrieved = 2100, } impl ToString for EventType { fn to_string(&self) -> String { match self { - Self::Variant1000 => String::from("1000"), - Self::Variant1001 => String::from("1001"), - Self::Variant1002 => String::from("1002"), - Self::Variant1003 => String::from("1003"), - Self::Variant1004 => String::from("1004"), - Self::Variant1005 => String::from("1005"), - Self::Variant1006 => String::from("1006"), - Self::Variant1007 => String::from("1007"), - Self::Variant1008 => String::from("1008"), - Self::Variant1009 => String::from("1009"), - Self::Variant1100 => String::from("1100"), - Self::Variant1101 => String::from("1101"), - Self::Variant1102 => String::from("1102"), - Self::Variant1103 => String::from("1103"), - Self::Variant1104 => String::from("1104"), - Self::Variant1105 => String::from("1105"), - Self::Variant1106 => String::from("1106"), - Self::Variant1107 => String::from("1107"), - Self::Variant1108 => String::from("1108"), - Self::Variant1109 => String::from("1109"), - Self::Variant1110 => String::from("1110"), - Self::Variant1111 => String::from("1111"), - Self::Variant1112 => String::from("1112"), - Self::Variant1113 => String::from("1113"), - Self::Variant1114 => String::from("1114"), - Self::Variant1115 => String::from("1115"), - Self::Variant1116 => String::from("1116"), - Self::Variant1117 => String::from("1117"), - Self::Variant1300 => String::from("1300"), - Self::Variant1301 => String::from("1301"), - Self::Variant1302 => String::from("1302"), - Self::Variant1400 => String::from("1400"), - Self::Variant1401 => String::from("1401"), - Self::Variant1402 => String::from("1402"), - Self::Variant1500 => String::from("1500"), - Self::Variant1501 => String::from("1501"), - Self::Variant1502 => String::from("1502"), - Self::Variant1503 => String::from("1503"), - Self::Variant1504 => String::from("1504"), - Self::Variant1505 => String::from("1505"), - Self::Variant1506 => String::from("1506"), - Self::Variant1507 => String::from("1507"), - Self::Variant1508 => String::from("1508"), - Self::Variant1509 => String::from("1509"), - Self::Variant1510 => String::from("1510"), - Self::Variant1511 => String::from("1511"), - Self::Variant1512 => String::from("1512"), - Self::Variant1600 => String::from("1600"), - Self::Variant1601 => String::from("1601"), - Self::Variant1602 => String::from("1602"), - Self::Variant1603 => String::from("1603"), - Self::Variant1604 => String::from("1604"), - Self::Variant1605 => String::from("1605"), - Self::Variant1606 => String::from("1606"), - Self::Variant1607 => String::from("1607"), - Self::Variant1608 => String::from("1608"), - Self::Variant1700 => String::from("1700"), - Self::Variant1800 => String::from("1800"), - Self::Variant1801 => String::from("1801"), - Self::Variant1802 => String::from("1802"), - Self::Variant1803 => String::from("1803"), - Self::Variant1900 => String::from("1900"), - Self::Variant1901 => String::from("1901"), - Self::Variant1902 => String::from("1902"), - Self::Variant1903 => String::from("1903"), - Self::Variant2000 => String::from("2000"), - Self::Variant2001 => String::from("2001"), - Self::Variant2002 => String::from("2002"), - Self::Variant2003 => String::from("2003"), - Self::Variant2100 => String::from("2100"), + Self::User_LoggedIn => String::from("1000"), + Self::User_ChangedPassword => String::from("1001"), + Self::User_Updated2fa => String::from("1002"), + Self::User_Disabled2fa => String::from("1003"), + Self::User_Recovered2fa => String::from("1004"), + Self::User_FailedLogIn => String::from("1005"), + Self::User_FailedLogIn2fa => String::from("1006"), + Self::User_ClientExportedVault => String::from("1007"), + Self::User_UpdatedTempPassword => String::from("1008"), + Self::User_MigratedKeyToKeyConnector => String::from("1009"), + Self::User_RequestedDeviceApproval => String::from("1010"), + Self::Cipher_Created => String::from("1100"), + Self::Cipher_Updated => String::from("1101"), + Self::Cipher_Deleted => String::from("1102"), + Self::Cipher_AttachmentCreated => String::from("1103"), + Self::Cipher_AttachmentDeleted => String::from("1104"), + Self::Cipher_Shared => String::from("1105"), + Self::Cipher_UpdatedCollections => String::from("1106"), + Self::Cipher_ClientViewed => String::from("1107"), + Self::Cipher_ClientToggledPasswordVisible => String::from("1108"), + Self::Cipher_ClientToggledHiddenFieldVisible => String::from("1109"), + Self::Cipher_ClientToggledCardCodeVisible => String::from("1110"), + Self::Cipher_ClientCopiedPassword => String::from("1111"), + Self::Cipher_ClientCopiedHiddenField => String::from("1112"), + Self::Cipher_ClientCopiedCardCode => String::from("1113"), + Self::Cipher_ClientAutofilled => String::from("1114"), + Self::Cipher_SoftDeleted => String::from("1115"), + Self::Cipher_Restored => String::from("1116"), + Self::Cipher_ClientToggledCardNumberVisible => String::from("1117"), + Self::Collection_Created => String::from("1300"), + Self::Collection_Updated => String::from("1301"), + Self::Collection_Deleted => String::from("1302"), + Self::Group_Created => String::from("1400"), + Self::Group_Updated => String::from("1401"), + Self::Group_Deleted => String::from("1402"), + Self::OrganizationUser_Invited => String::from("1500"), + Self::OrganizationUser_Confirmed => String::from("1501"), + Self::OrganizationUser_Updated => String::from("1502"), + Self::OrganizationUser_Removed => String::from("1503"), + Self::OrganizationUser_UpdatedGroups => String::from("1504"), + Self::OrganizationUser_UnlinkedSso => String::from("1505"), + Self::OrganizationUser_ResetPassword_Enroll => String::from("1506"), + Self::OrganizationUser_ResetPassword_Withdraw => String::from("1507"), + Self::OrganizationUser_AdminResetPassword => String::from("1508"), + Self::OrganizationUser_ResetSsoLink => String::from("1509"), + Self::OrganizationUser_FirstSsoLogin => String::from("1510"), + Self::OrganizationUser_Revoked => String::from("1511"), + Self::OrganizationUser_Restored => String::from("1512"), + Self::OrganizationUser_ApprovedAuthRequest => String::from("1513"), + Self::OrganizationUser_RejectedAuthRequest => String::from("1514"), + Self::Organization_Updated => String::from("1600"), + Self::Organization_PurgedVault => String::from("1601"), + Self::Organization_ClientExportedVault => String::from("1602"), + Self::Organization_VaultAccessed => String::from("1603"), + Self::Organization_EnabledSso => String::from("1604"), + Self::Organization_DisabledSso => String::from("1605"), + Self::Organization_EnabledKeyConnector => String::from("1606"), + Self::Organization_DisabledKeyConnector => String::from("1607"), + Self::Organization_SponsorshipsSynced => String::from("1608"), + Self::Organization_CollectionManagement_Updated => String::from("1609"), + Self::Policy_Updated => String::from("1700"), + Self::ProviderUser_Invited => String::from("1800"), + Self::ProviderUser_Confirmed => String::from("1801"), + Self::ProviderUser_Updated => String::from("1802"), + Self::ProviderUser_Removed => String::from("1803"), + Self::ProviderOrganization_Created => String::from("1900"), + Self::ProviderOrganization_Added => String::from("1901"), + Self::ProviderOrganization_Removed => String::from("1902"), + Self::ProviderOrganization_VaultAccessed => String::from("1903"), + Self::OrganizationDomain_Added => String::from("2000"), + Self::OrganizationDomain_Removed => String::from("2001"), + Self::OrganizationDomain_Verified => String::from("2002"), + Self::OrganizationDomain_NotVerified => String::from("2003"), + Self::Secret_Retrieved => String::from("2100"), } } } impl Default for EventType { fn default() -> EventType { - Self::Variant1000 + Self::User_LoggedIn } } diff --git a/crates/bitwarden-api-api/src/models/inner_project.rs b/crates/bitwarden-api-api/src/models/fido2_user.rs similarity index 63% rename from crates/bitwarden-api-api/src/models/inner_project.rs rename to crates/bitwarden-api-api/src/models/fido2_user.rs index 59ecb7f07..542019d39 100644 --- a/crates/bitwarden-api-api/src/models/inner_project.rs +++ b/crates/bitwarden-api-api/src/models/fido2_user.rs @@ -8,19 +8,22 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct InnerProject { - #[serde(rename = "id", skip_serializing_if = "Option::is_none")] - pub id: Option, +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Fido2User { #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "displayName", skip_serializing_if = "Option::is_none")] + pub display_name: Option, } -impl InnerProject { - pub fn new() -> InnerProject { - InnerProject { - id: None, +impl Fido2User { + pub fn new() -> Fido2User { + Fido2User { name: None, + id: None, + display_name: None, } } } diff --git a/crates/bitwarden-api-api/src/models/field_type.rs b/crates/bitwarden-api-api/src/models/field_type.rs index fbaa9b728..3a6209399 100644 --- a/crates/bitwarden-api-api/src/models/field_type.rs +++ b/crates/bitwarden-api-api/src/models/field_type.rs @@ -14,25 +14,25 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum FieldType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, + Text = 0, + Hidden = 1, + Boolean = 2, + Linked = 3, } impl ToString for FieldType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), + Self::Text => String::from("0"), + Self::Hidden => String::from("1"), + Self::Boolean => String::from("2"), + Self::Linked => String::from("3"), } } } impl Default for FieldType { fn default() -> FieldType { - Self::Variant0 + Self::Text } } diff --git a/crates/bitwarden-api-api/src/models/file_upload_type.rs b/crates/bitwarden-api-api/src/models/file_upload_type.rs index 765bc7563..79327b3cb 100644 --- a/crates/bitwarden-api-api/src/models/file_upload_type.rs +++ b/crates/bitwarden-api-api/src/models/file_upload_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum FileUploadType { - Variant0 = 0, - Variant1 = 1, + Direct = 0, + Azure = 1, } impl ToString for FileUploadType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::Direct => String::from("0"), + Self::Azure => String::from("1"), } } } impl Default for FileUploadType { fn default() -> FileUploadType { - Self::Variant0 + Self::Direct } } diff --git a/crates/bitwarden-api-api/src/models/folder_request_model.rs b/crates/bitwarden-api-api/src/models/folder_request_model.rs index d226d2e04..cb73d7693 100644 --- a/crates/bitwarden-api-api/src/models/folder_request_model.rs +++ b/crates/bitwarden-api-api/src/models/folder_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct FolderRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/folder_response_model.rs b/crates/bitwarden-api-api/src/models/folder_response_model.rs index 976340c98..705c45f04 100644 --- a/crates/bitwarden-api-api/src/models/folder_response_model.rs +++ b/crates/bitwarden-api-api/src/models/folder_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct FolderResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/folder_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/folder_response_model_list_response_model.rs index d0209006d..3a12dfd31 100644 --- a/crates/bitwarden-api-api/src/models/folder_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/folder_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct FolderResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/folder_with_id_request_model.rs b/crates/bitwarden-api-api/src/models/folder_with_id_request_model.rs index 599126620..a69b5ef8c 100644 --- a/crates/bitwarden-api-api/src/models/folder_with_id_request_model.rs +++ b/crates/bitwarden-api-api/src/models/folder_with_id_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct FolderWithIdRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/get_secrets_request_model.rs b/crates/bitwarden-api-api/src/models/get_secrets_request_model.rs index 2786d8c9d..93ca67bde 100644 --- a/crates/bitwarden-api-api/src/models/get_secrets_request_model.rs +++ b/crates/bitwarden-api-api/src/models/get_secrets_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GetSecretsRequestModel { #[serde(rename = "ids")] pub ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/global_domains.rs b/crates/bitwarden-api-api/src/models/global_domains.rs index f0582caa4..b86399762 100644 --- a/crates/bitwarden-api-api/src/models/global_domains.rs +++ b/crates/bitwarden-api-api/src/models/global_domains.rs @@ -8,10 +8,10 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GlobalDomains { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub r#type: Option, + pub r#type: Option, #[serde(rename = "domains", skip_serializing_if = "Option::is_none")] pub domains: Option>, #[serde(rename = "excluded", skip_serializing_if = "Option::is_none")] diff --git a/crates/bitwarden-api-api/src/models/global_equivalent_domains_type.rs b/crates/bitwarden-api-api/src/models/global_equivalent_domains_type.rs index f5e1c3655..f30b2d5e7 100644 --- a/crates/bitwarden-api-api/src/models/global_equivalent_domains_type.rs +++ b/crates/bitwarden-api-api/src/models/global_equivalent_domains_type.rs @@ -14,199 +14,199 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum GlobalEquivalentDomainsType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, - Variant8 = 8, - Variant9 = 9, - Variant10 = 10, - Variant11 = 11, - Variant12 = 12, - Variant13 = 13, - Variant14 = 14, - Variant15 = 15, - Variant16 = 16, - Variant17 = 17, - Variant18 = 18, - Variant19 = 19, - Variant20 = 20, - Variant21 = 21, - Variant22 = 22, - Variant23 = 23, - Variant24 = 24, - Variant25 = 25, - Variant26 = 26, - Variant27 = 27, - Variant28 = 28, - Variant29 = 29, - Variant30 = 30, - Variant31 = 31, - Variant32 = 32, - Variant33 = 33, - Variant34 = 34, - Variant35 = 35, - Variant36 = 36, - Variant37 = 37, - Variant38 = 38, - Variant39 = 39, - Variant40 = 40, - Variant41 = 41, - Variant42 = 42, - Variant43 = 43, - Variant44 = 44, - Variant45 = 45, - Variant46 = 46, - Variant47 = 47, - Variant48 = 48, - Variant49 = 49, - Variant50 = 50, - Variant51 = 51, - Variant52 = 52, - Variant53 = 53, - Variant54 = 54, - Variant55 = 55, - Variant56 = 56, - Variant57 = 57, - Variant58 = 58, - Variant59 = 59, - Variant60 = 60, - Variant61 = 61, - Variant62 = 62, - Variant63 = 63, - Variant64 = 64, - Variant65 = 65, - Variant66 = 66, - Variant67 = 67, - Variant68 = 68, - Variant69 = 69, - Variant70 = 70, - Variant71 = 71, - Variant72 = 72, - Variant73 = 73, - Variant74 = 74, - Variant75 = 75, - Variant76 = 76, - Variant77 = 77, - Variant78 = 78, - Variant79 = 79, - Variant80 = 80, - Variant81 = 81, - Variant82 = 82, - Variant83 = 83, - Variant84 = 84, - Variant85 = 85, - Variant86 = 86, - Variant87 = 87, - Variant88 = 88, - Variant89 = 89, - Variant90 = 90, + Google = 0, + Apple = 1, + Ameritrade = 2, + BoA = 3, + Sprint = 4, + WellsFargo = 5, + Merrill = 6, + Citi = 7, + Cnet = 8, + Gap = 9, + Microsoft = 10, + United = 11, + Yahoo = 12, + Zonelabs = 13, + PayPal = 14, + Avon = 15, + Diapers = 16, + Contacts = 17, + Amazon = 18, + Cox = 19, + Norton = 20, + Verizon = 21, + Buy = 22, + Sirius = 23, + Ea = 24, + Basecamp = 25, + Steam = 26, + Chart = 27, + Gotomeeting = 28, + Gogo = 29, + Oracle = 30, + Discover = 31, + Dcu = 32, + Healthcare = 33, + Pepco = 34, + Century21 = 35, + Comcast = 36, + Cricket = 37, + Mtb = 38, + Dropbox = 39, + Snapfish = 40, + Alibaba = 41, + Playstation = 42, + Mercado = 43, + Zendesk = 44, + Autodesk = 45, + RailNation = 46, + Wpcu = 47, + Mathletics = 48, + Discountbank = 49, + Mi = 50, + Facebook = 51, + Postepay = 52, + Skysports = 53, + Disney = 54, + Pokemon = 55, + Uv = 56, + Yahavo = 57, + Mdsol = 58, + Sears = 59, + Xiami = 60, + Belkin = 61, + Turbotax = 62, + Shopify = 63, + Ebay = 64, + Techdata = 65, + Schwab = 66, + Mozilla = 67, + Tesla = 68, + MorganStanley = 69, + TaxAct = 70, + Wikimedia = 71, + Airbnb = 72, + Eventbrite = 73, + StackExchange = 74, + Docusign = 75, + Envato = 76, + X10Hosting = 77, + Cisco = 78, + CedarFair = 79, + Ubiquiti = 80, + Discord = 81, + Netcup = 82, + Yandex = 83, + Sony = 84, + Proton = 85, + Ubisoft = 86, + TransferWise = 87, + TakeawayEU = 88, + Atlassian = 89, + Pinterest = 90, } impl ToString for GlobalEquivalentDomainsType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), - Self::Variant8 => String::from("8"), - Self::Variant9 => String::from("9"), - Self::Variant10 => String::from("10"), - Self::Variant11 => String::from("11"), - Self::Variant12 => String::from("12"), - Self::Variant13 => String::from("13"), - Self::Variant14 => String::from("14"), - Self::Variant15 => String::from("15"), - Self::Variant16 => String::from("16"), - Self::Variant17 => String::from("17"), - Self::Variant18 => String::from("18"), - Self::Variant19 => String::from("19"), - Self::Variant20 => String::from("20"), - Self::Variant21 => String::from("21"), - Self::Variant22 => String::from("22"), - Self::Variant23 => String::from("23"), - Self::Variant24 => String::from("24"), - Self::Variant25 => String::from("25"), - Self::Variant26 => String::from("26"), - Self::Variant27 => String::from("27"), - Self::Variant28 => String::from("28"), - Self::Variant29 => String::from("29"), - Self::Variant30 => String::from("30"), - Self::Variant31 => String::from("31"), - Self::Variant32 => String::from("32"), - Self::Variant33 => String::from("33"), - Self::Variant34 => String::from("34"), - Self::Variant35 => String::from("35"), - Self::Variant36 => String::from("36"), - Self::Variant37 => String::from("37"), - Self::Variant38 => String::from("38"), - Self::Variant39 => String::from("39"), - Self::Variant40 => String::from("40"), - Self::Variant41 => String::from("41"), - Self::Variant42 => String::from("42"), - Self::Variant43 => String::from("43"), - Self::Variant44 => String::from("44"), - Self::Variant45 => String::from("45"), - Self::Variant46 => String::from("46"), - Self::Variant47 => String::from("47"), - Self::Variant48 => String::from("48"), - Self::Variant49 => String::from("49"), - Self::Variant50 => String::from("50"), - Self::Variant51 => String::from("51"), - Self::Variant52 => String::from("52"), - Self::Variant53 => String::from("53"), - Self::Variant54 => String::from("54"), - Self::Variant55 => String::from("55"), - Self::Variant56 => String::from("56"), - Self::Variant57 => String::from("57"), - Self::Variant58 => String::from("58"), - Self::Variant59 => String::from("59"), - Self::Variant60 => String::from("60"), - Self::Variant61 => String::from("61"), - Self::Variant62 => String::from("62"), - Self::Variant63 => String::from("63"), - Self::Variant64 => String::from("64"), - Self::Variant65 => String::from("65"), - Self::Variant66 => String::from("66"), - Self::Variant67 => String::from("67"), - Self::Variant68 => String::from("68"), - Self::Variant69 => String::from("69"), - Self::Variant70 => String::from("70"), - Self::Variant71 => String::from("71"), - Self::Variant72 => String::from("72"), - Self::Variant73 => String::from("73"), - Self::Variant74 => String::from("74"), - Self::Variant75 => String::from("75"), - Self::Variant76 => String::from("76"), - Self::Variant77 => String::from("77"), - Self::Variant78 => String::from("78"), - Self::Variant79 => String::from("79"), - Self::Variant80 => String::from("80"), - Self::Variant81 => String::from("81"), - Self::Variant82 => String::from("82"), - Self::Variant83 => String::from("83"), - Self::Variant84 => String::from("84"), - Self::Variant85 => String::from("85"), - Self::Variant86 => String::from("86"), - Self::Variant87 => String::from("87"), - Self::Variant88 => String::from("88"), - Self::Variant89 => String::from("89"), - Self::Variant90 => String::from("90"), + Self::Google => String::from("0"), + Self::Apple => String::from("1"), + Self::Ameritrade => String::from("2"), + Self::BoA => String::from("3"), + Self::Sprint => String::from("4"), + Self::WellsFargo => String::from("5"), + Self::Merrill => String::from("6"), + Self::Citi => String::from("7"), + Self::Cnet => String::from("8"), + Self::Gap => String::from("9"), + Self::Microsoft => String::from("10"), + Self::United => String::from("11"), + Self::Yahoo => String::from("12"), + Self::Zonelabs => String::from("13"), + Self::PayPal => String::from("14"), + Self::Avon => String::from("15"), + Self::Diapers => String::from("16"), + Self::Contacts => String::from("17"), + Self::Amazon => String::from("18"), + Self::Cox => String::from("19"), + Self::Norton => String::from("20"), + Self::Verizon => String::from("21"), + Self::Buy => String::from("22"), + Self::Sirius => String::from("23"), + Self::Ea => String::from("24"), + Self::Basecamp => String::from("25"), + Self::Steam => String::from("26"), + Self::Chart => String::from("27"), + Self::Gotomeeting => String::from("28"), + Self::Gogo => String::from("29"), + Self::Oracle => String::from("30"), + Self::Discover => String::from("31"), + Self::Dcu => String::from("32"), + Self::Healthcare => String::from("33"), + Self::Pepco => String::from("34"), + Self::Century21 => String::from("35"), + Self::Comcast => String::from("36"), + Self::Cricket => String::from("37"), + Self::Mtb => String::from("38"), + Self::Dropbox => String::from("39"), + Self::Snapfish => String::from("40"), + Self::Alibaba => String::from("41"), + Self::Playstation => String::from("42"), + Self::Mercado => String::from("43"), + Self::Zendesk => String::from("44"), + Self::Autodesk => String::from("45"), + Self::RailNation => String::from("46"), + Self::Wpcu => String::from("47"), + Self::Mathletics => String::from("48"), + Self::Discountbank => String::from("49"), + Self::Mi => String::from("50"), + Self::Facebook => String::from("51"), + Self::Postepay => String::from("52"), + Self::Skysports => String::from("53"), + Self::Disney => String::from("54"), + Self::Pokemon => String::from("55"), + Self::Uv => String::from("56"), + Self::Yahavo => String::from("57"), + Self::Mdsol => String::from("58"), + Self::Sears => String::from("59"), + Self::Xiami => String::from("60"), + Self::Belkin => String::from("61"), + Self::Turbotax => String::from("62"), + Self::Shopify => String::from("63"), + Self::Ebay => String::from("64"), + Self::Techdata => String::from("65"), + Self::Schwab => String::from("66"), + Self::Mozilla => String::from("67"), + Self::Tesla => String::from("68"), + Self::MorganStanley => String::from("69"), + Self::TaxAct => String::from("70"), + Self::Wikimedia => String::from("71"), + Self::Airbnb => String::from("72"), + Self::Eventbrite => String::from("73"), + Self::StackExchange => String::from("74"), + Self::Docusign => String::from("75"), + Self::Envato => String::from("76"), + Self::X10Hosting => String::from("77"), + Self::Cisco => String::from("78"), + Self::CedarFair => String::from("79"), + Self::Ubiquiti => String::from("80"), + Self::Discord => String::from("81"), + Self::Netcup => String::from("82"), + Self::Yandex => String::from("83"), + Self::Sony => String::from("84"), + Self::Proton => String::from("85"), + Self::Ubisoft => String::from("86"), + Self::TransferWise => String::from("87"), + Self::TakeawayEU => String::from("88"), + Self::Atlassian => String::from("89"), + Self::Pinterest => String::from("90"), } } } impl Default for GlobalEquivalentDomainsType { fn default() -> GlobalEquivalentDomainsType { - Self::Variant0 + Self::Google } } diff --git a/crates/bitwarden-api-api/src/models/granted_access_policy_request.rs b/crates/bitwarden-api-api/src/models/granted_access_policy_request.rs index 4d4f1291d..3d35274a0 100644 --- a/crates/bitwarden-api-api/src/models/granted_access_policy_request.rs +++ b/crates/bitwarden-api-api/src/models/granted_access_policy_request.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GrantedAccessPolicyRequest { #[serde(rename = "grantedId")] pub granted_id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/models/group.rs b/crates/bitwarden-api-api/src/models/group.rs index c7acafc56..7133029b5 100644 --- a/crates/bitwarden-api-api/src/models/group.rs +++ b/crates/bitwarden-api-api/src/models/group.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Group { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/group_bulk_request_model.rs b/crates/bitwarden-api-api/src/models/group_bulk_request_model.rs index 1f56fe0fb..432f9f6d8 100644 --- a/crates/bitwarden-api-api/src/models/group_bulk_request_model.rs +++ b/crates/bitwarden-api-api/src/models/group_bulk_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GroupBulkRequestModel { #[serde(rename = "ids")] pub ids: Vec, 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 91e20d584..d6ee7239a 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GroupDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/group_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/group_details_response_model_list_response_model.rs index 5aa54b57c..c1c19d332 100644 --- a/crates/bitwarden-api-api/src/models/group_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/group_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GroupDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/group_project_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/group_project_access_policy_response_model.rs index d43b70e95..2b6f5c9c0 100644 --- a/crates/bitwarden-api-api/src/models/group_project_access_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/group_project_access_policy_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GroupProjectAccessPolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 27d24defc..0284a04d9 100644 --- a/crates/bitwarden-api-api/src/models/group_request_model.rs +++ b/crates/bitwarden-api-api/src/models/group_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GroupRequestModel { #[serde(rename = "name")] pub name: String, 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 32d43b51f..388fe68f5 100644 --- a/crates/bitwarden-api-api/src/models/group_response_model.rs +++ b/crates/bitwarden-api-api/src/models/group_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GroupResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 index 0f7eb9b3e..af04b2cfe 100644 --- 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GroupServiceAccountAccessPolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/iap_check_request_model.rs b/crates/bitwarden-api-api/src/models/iap_check_request_model.rs deleted file mode 100644 index f18b4b017..000000000 --- a/crates/bitwarden-api-api/src/models/iap_check_request_model.rs +++ /dev/null @@ -1,23 +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 - */ - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct IapCheckRequestModel { - #[serde(rename = "paymentMethodType")] - pub payment_method_type: crate::models::PaymentMethodType, -} - -impl IapCheckRequestModel { - pub fn new(payment_method_type: crate::models::PaymentMethodType) -> IapCheckRequestModel { - IapCheckRequestModel { - payment_method_type, - } - } -} diff --git a/crates/bitwarden-api-api/src/models/import_ciphers_request_model.rs b/crates/bitwarden-api-api/src/models/import_ciphers_request_model.rs index 9cc2a7d75..371f2fa5c 100644 --- a/crates/bitwarden-api-api/src/models/import_ciphers_request_model.rs +++ b/crates/bitwarden-api-api/src/models/import_ciphers_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ImportCiphersRequestModel { #[serde(rename = "folders", skip_serializing_if = "Option::is_none")] pub folders: Option>, diff --git a/crates/bitwarden-api-api/src/models/import_organization_ciphers_request_model.rs b/crates/bitwarden-api-api/src/models/import_organization_ciphers_request_model.rs index 625f733a3..894fb7131 100644 --- a/crates/bitwarden-api-api/src/models/import_organization_ciphers_request_model.rs +++ b/crates/bitwarden-api-api/src/models/import_organization_ciphers_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ImportOrganizationCiphersRequestModel { #[serde(rename = "collections", skip_serializing_if = "Option::is_none")] pub collections: Option>, diff --git a/crates/bitwarden-api-api/src/models/import_organization_users_request_model.rs b/crates/bitwarden-api-api/src/models/import_organization_users_request_model.rs index 72f276d05..ff5be1b6f 100644 --- a/crates/bitwarden-api-api/src/models/import_organization_users_request_model.rs +++ b/crates/bitwarden-api-api/src/models/import_organization_users_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ImportOrganizationUsersRequestModel { #[serde(rename = "groups", skip_serializing_if = "Option::is_none")] pub groups: Option>, diff --git a/crates/bitwarden-api-api/src/models/inner_project_export_response_model.rs b/crates/bitwarden-api-api/src/models/inner_project_export_response_model.rs index 2c14b4d3c..a34dab3a6 100644 --- a/crates/bitwarden-api-api/src/models/inner_project_export_response_model.rs +++ b/crates/bitwarden-api-api/src/models/inner_project_export_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct InnerProjectExportResponseModel { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/inner_project_import_request_model.rs b/crates/bitwarden-api-api/src/models/inner_project_import_request_model.rs index 1f3b7614d..808504d83 100644 --- a/crates/bitwarden-api-api/src/models/inner_project_import_request_model.rs +++ b/crates/bitwarden-api-api/src/models/inner_project_import_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct InnerProjectImportRequestModel { #[serde(rename = "id")] pub id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/models/inner_secret_export_response_model.rs b/crates/bitwarden-api-api/src/models/inner_secret_export_response_model.rs index d735029b9..7a55ad679 100644 --- a/crates/bitwarden-api-api/src/models/inner_secret_export_response_model.rs +++ b/crates/bitwarden-api-api/src/models/inner_secret_export_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct InnerSecretExportResponseModel { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/inner_secret_import_request_model.rs b/crates/bitwarden-api-api/src/models/inner_secret_import_request_model.rs index 594f14cce..bfe164eff 100644 --- a/crates/bitwarden-api-api/src/models/inner_secret_import_request_model.rs +++ b/crates/bitwarden-api-api/src/models/inner_secret_import_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct InnerSecretImportRequestModel { #[serde(rename = "id")] pub id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/models/installation_request_model.rs b/crates/bitwarden-api-api/src/models/installation_request_model.rs index a8abba2d6..58b8924ef 100644 --- a/crates/bitwarden-api-api/src/models/installation_request_model.rs +++ b/crates/bitwarden-api-api/src/models/installation_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct InstallationRequestModel { #[serde(rename = "email")] pub email: String, diff --git a/crates/bitwarden-api-api/src/models/installation_response_model.rs b/crates/bitwarden-api-api/src/models/installation_response_model.rs index 4fd14dce8..0d7009ff1 100644 --- a/crates/bitwarden-api-api/src/models/installation_response_model.rs +++ b/crates/bitwarden-api-api/src/models/installation_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct InstallationResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/int32_int32_key_value_pair.rs b/crates/bitwarden-api-api/src/models/int32_int32_key_value_pair.rs index 3ca10d2b3..831f0e03a 100644 --- a/crates/bitwarden-api-api/src/models/int32_int32_key_value_pair.rs +++ b/crates/bitwarden-api-api/src/models/int32_int32_key_value_pair.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Int32Int32KeyValuePair { #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option, diff --git a/crates/bitwarden-api-api/src/models/kdf_request_model.rs b/crates/bitwarden-api-api/src/models/kdf_request_model.rs index bb903a84f..2f43e2f45 100644 --- a/crates/bitwarden-api-api/src/models/kdf_request_model.rs +++ b/crates/bitwarden-api-api/src/models/kdf_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct KdfRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/kdf_type.rs b/crates/bitwarden-api-api/src/models/kdf_type.rs index 83b611680..e371ee156 100644 --- a/crates/bitwarden-api-api/src/models/kdf_type.rs +++ b/crates/bitwarden-api-api/src/models/kdf_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum KdfType { - Variant0 = 0, - Variant1 = 1, + PBKDF2_SHA256 = 0, + Argon2id = 1, } impl ToString for KdfType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::PBKDF2_SHA256 => String::from("0"), + Self::Argon2id => String::from("1"), } } } impl Default for KdfType { fn default() -> KdfType { - Self::Variant0 + Self::PBKDF2_SHA256 } } diff --git a/crates/bitwarden-api-api/src/models/key_model.rs b/crates/bitwarden-api-api/src/models/key_model.rs index 20aa497de..78231697b 100644 --- a/crates/bitwarden-api-api/src/models/key_model.rs +++ b/crates/bitwarden-api-api/src/models/key_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct KeyModel { #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, diff --git a/crates/bitwarden-api-api/src/models/keys_request_model.rs b/crates/bitwarden-api-api/src/models/keys_request_model.rs index f1db24fc9..d87329136 100644 --- a/crates/bitwarden-api-api/src/models/keys_request_model.rs +++ b/crates/bitwarden-api-api/src/models/keys_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct KeysRequestModel { #[serde(rename = "publicKey", skip_serializing_if = "Option::is_none")] pub public_key: Option, diff --git a/crates/bitwarden-api-api/src/models/keys_response_model.rs b/crates/bitwarden-api-api/src/models/keys_response_model.rs index 82f3e8804..21b998384 100644 --- a/crates/bitwarden-api-api/src/models/keys_response_model.rs +++ b/crates/bitwarden-api-api/src/models/keys_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct KeysResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/license_type.rs b/crates/bitwarden-api-api/src/models/license_type.rs index 0f887fa79..178b3433f 100644 --- a/crates/bitwarden-api-api/src/models/license_type.rs +++ b/crates/bitwarden-api-api/src/models/license_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum LicenseType { - Variant0 = 0, - Variant1 = 1, + User = 0, + Organization = 1, } impl ToString for LicenseType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::User => String::from("0"), + Self::Organization => String::from("1"), } } } impl Default for LicenseType { fn default() -> LicenseType { - Self::Variant0 + Self::User } } diff --git a/crates/bitwarden-api-api/src/models/master_password_policy_response_model.rs b/crates/bitwarden-api-api/src/models/master_password_policy_response_model.rs index 356a61f34..b1a53bdea 100644 --- a/crates/bitwarden-api-api/src/models/master_password_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/master_password_policy_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MasterPasswordPolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/member_decryption_type.rs b/crates/bitwarden-api-api/src/models/member_decryption_type.rs index e36344a9f..dba1b793b 100644 --- a/crates/bitwarden-api-api/src/models/member_decryption_type.rs +++ b/crates/bitwarden-api-api/src/models/member_decryption_type.rs @@ -14,23 +14,23 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum MemberDecryptionType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, + MasterPassword = 0, + KeyConnector = 1, + TrustedDeviceEncryption = 2, } impl ToString for MemberDecryptionType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::MasterPassword => String::from("0"), + Self::KeyConnector => String::from("1"), + Self::TrustedDeviceEncryption => String::from("2"), } } } impl Default for MemberDecryptionType { fn default() -> MemberDecryptionType { - Self::Variant0 + Self::MasterPassword } } diff --git a/crates/bitwarden-api-api/src/models/mod.rs b/crates/bitwarden-api-api/src/models/mod.rs index 9d2aa9ae8..8bed9f858 100644 --- a/crates/bitwarden-api-api/src/models/mod.rs +++ b/crates/bitwarden-api-api/src/models/mod.rs @@ -14,14 +14,22 @@ pub mod access_token_response_model_list_response_model; pub use self::access_token_response_model_list_response_model::AccessTokenResponseModelListResponseModel; pub mod admin_auth_request_update_request_model; pub use self::admin_auth_request_update_request_model::AdminAuthRequestUpdateRequestModel; +pub mod algorithm; +pub use self::algorithm::Algorithm; pub mod api_key_response_model; pub use self::api_key_response_model::ApiKeyResponseModel; +pub mod assertion_options; +pub use self::assertion_options::AssertionOptions; +pub mod assertion_response; +pub use self::assertion_response::AssertionResponse; pub mod attachment_request_model; pub use self::attachment_request_model::AttachmentRequestModel; pub mod attachment_response_model; pub use self::attachment_response_model::AttachmentResponseModel; pub mod attachment_upload_data_response_model; pub use self::attachment_upload_data_response_model::AttachmentUploadDataResponseModel; +pub mod attestation_conveyance_preference; +pub use self::attestation_conveyance_preference::AttestationConveyancePreference; pub mod auth_request_create_request_model; pub use self::auth_request_create_request_model::AuthRequestCreateRequestModel; pub mod auth_request_response_model; @@ -32,16 +40,28 @@ pub mod auth_request_type; pub use self::auth_request_type::AuthRequestType; pub mod auth_request_update_request_model; pub use self::auth_request_update_request_model::AuthRequestUpdateRequestModel; +pub mod authentication_extensions_client_inputs; +pub use self::authentication_extensions_client_inputs::AuthenticationExtensionsClientInputs; pub mod authentication_extensions_client_outputs; pub use self::authentication_extensions_client_outputs::AuthenticationExtensionsClientOutputs; +pub mod authenticator_assertion_raw_response; +pub use self::authenticator_assertion_raw_response::AuthenticatorAssertionRawResponse; +pub mod authenticator_attachment; +pub use self::authenticator_attachment::AuthenticatorAttachment; pub mod authenticator_attestation_raw_response; pub use self::authenticator_attestation_raw_response::AuthenticatorAttestationRawResponse; +pub mod authenticator_selection; +pub use self::authenticator_selection::AuthenticatorSelection; +pub mod authenticator_transport; +pub use self::authenticator_transport::AuthenticatorTransport; pub mod base_access_policy_response_model; pub use self::base_access_policy_response_model::BaseAccessPolicyResponseModel; pub mod base_secret_response_model; pub use self::base_secret_response_model::BaseSecretResponseModel; pub mod base_secret_response_model_list_response_model; pub use self::base_secret_response_model_list_response_model::BaseSecretResponseModelListResponseModel; +pub mod billing_customer_discount; +pub use self::billing_customer_discount::BillingCustomerDiscount; pub mod billing_history_response_model; pub use self::billing_history_response_model::BillingHistoryResponseModel; pub mod billing_invoice; @@ -62,8 +82,8 @@ pub mod billing_transaction; pub use self::billing_transaction::BillingTransaction; pub mod bit_pay_invoice_request_model; pub use self::bit_pay_invoice_request_model::BitPayInvoiceRequestModel; -pub mod bitwarden_product_type; -pub use self::bitwarden_product_type::BitwardenProductType; +pub mod bulk_collection_access_request_model; +pub use self::bulk_collection_access_request_model::BulkCollectionAccessRequestModel; pub mod bulk_delete_response_model; pub use self::bulk_delete_response_model::BulkDeleteResponseModel; pub mod bulk_delete_response_model_list_response_model; @@ -90,6 +110,8 @@ pub mod cipher_details_response_model; pub use self::cipher_details_response_model::CipherDetailsResponseModel; pub mod cipher_details_response_model_list_response_model; pub use self::cipher_details_response_model_list_response_model::CipherDetailsResponseModelListResponseModel; +pub mod cipher_fido2_credential_model; +pub use self::cipher_fido2_credential_model::CipherFido2CredentialModel; pub mod cipher_field_model; pub use self::cipher_field_model::CipherFieldModel; pub mod cipher_identity_model; @@ -144,10 +166,14 @@ pub mod collection_with_id_request_model; pub use self::collection_with_id_request_model::CollectionWithIdRequestModel; pub mod config_response_model; pub use self::config_response_model::ConfigResponseModel; +pub mod credential_create_options; +pub use self::credential_create_options::CredentialCreateOptions; pub mod delete_recover_request_model; pub use self::delete_recover_request_model::DeleteRecoverRequestModel; pub mod device_keys_request_model; pub use self::device_keys_request_model::DeviceKeysRequestModel; +pub mod device_keys_update_request_model; +pub use self::device_keys_update_request_model::DeviceKeysUpdateRequestModel; pub mod device_request_model; pub use self::device_request_model::DeviceRequestModel; pub mod device_response_model; @@ -190,6 +216,8 @@ pub mod emergency_access_update_request_model; pub use self::emergency_access_update_request_model::EmergencyAccessUpdateRequestModel; pub mod emergency_access_view_response_model; pub use self::emergency_access_view_response_model::EmergencyAccessViewResponseModel; +pub mod emergency_access_with_id_request_model; +pub use self::emergency_access_with_id_request_model::EmergencyAccessWithIdRequestModel; pub mod environment_config_response_model; pub use self::environment_config_response_model::EnvironmentConfigResponseModel; pub mod event_response_model; @@ -200,6 +228,8 @@ pub mod event_system_user; pub use self::event_system_user::EventSystemUser; pub mod event_type; pub use self::event_type::EventType; +pub mod fido2_user; +pub use self::fido2_user::Fido2User; pub mod field_type; pub use self::field_type::FieldType; pub mod file_upload_type; @@ -236,8 +266,6 @@ 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 iap_check_request_model; -pub use self::iap_check_request_model::IapCheckRequestModel; pub mod import_ciphers_request_model; pub use self::import_ciphers_request_model::ImportCiphersRequestModel; pub mod import_organization_ciphers_request_model; @@ -286,6 +314,8 @@ pub mod organization_api_key_type; pub use self::organization_api_key_type::OrganizationApiKeyType; pub mod organization_auto_enroll_status_response_model; pub use self::organization_auto_enroll_status_response_model::OrganizationAutoEnrollStatusResponseModel; +pub mod organization_collection_management_update_request_model; +pub use self::organization_collection_management_update_request_model::OrganizationCollectionManagementUpdateRequestModel; pub mod organization_connection_request_model; pub use self::organization_connection_request_model::OrganizationConnectionRequestModel; pub mod organization_connection_response_model; @@ -304,16 +334,18 @@ pub mod organization_domain_sso_details_request_model; pub use self::organization_domain_sso_details_request_model::OrganizationDomainSsoDetailsRequestModel; pub mod organization_domain_sso_details_response_model; pub use self::organization_domain_sso_details_response_model::OrganizationDomainSsoDetailsResponseModel; -pub mod organization_enroll_secrets_manager_request_model; -pub use self::organization_enroll_secrets_manager_request_model::OrganizationEnrollSecretsManagerRequestModel; pub mod organization_keys_request_model; pub use self::organization_keys_request_model::OrganizationKeysRequestModel; pub mod organization_keys_response_model; pub use self::organization_keys_response_model::OrganizationKeysResponseModel; pub mod organization_license; pub use self::organization_license::OrganizationLicense; +pub mod organization_public_key_response_model; +pub use self::organization_public_key_response_model::OrganizationPublicKeyResponseModel; pub mod organization_response_model; pub use self::organization_response_model::OrganizationResponseModel; +pub mod organization_risks_subscription_failure_response_model; +pub use self::organization_risks_subscription_failure_response_model::OrganizationRisksSubscriptionFailureResponseModel; pub mod organization_seat_request_model; pub use self::organization_seat_request_model::OrganizationSeatRequestModel; pub mod organization_sponsorship_create_request_model; @@ -386,8 +418,12 @@ pub mod organization_user_user_details_response_model_list_response_model; pub use self::organization_user_user_details_response_model_list_response_model::OrganizationUserUserDetailsResponseModelListResponseModel; pub mod organization_verify_bank_request_model; pub use self::organization_verify_bank_request_model::OrganizationVerifyBankRequestModel; +pub mod other_device_keys_update_request_model; +pub use self::other_device_keys_update_request_model::OtherDeviceKeysUpdateRequestModel; pub mod password_hint_request_model; pub use self::password_hint_request_model::PasswordHintRequestModel; +pub mod password_manager_plan_features_response_model; +pub use self::password_manager_plan_features_response_model::PasswordManagerPlanFeaturesResponseModel; pub mod password_request_model; pub use self::password_request_model::PasswordRequestModel; pub mod payment_method_type; @@ -400,6 +436,8 @@ pub mod pending_organization_auth_request_response_model; pub use self::pending_organization_auth_request_response_model::PendingOrganizationAuthRequestResponseModel; pub mod pending_organization_auth_request_response_model_list_response_model; pub use self::pending_organization_auth_request_response_model_list_response_model::PendingOrganizationAuthRequestResponseModelListResponseModel; +pub mod people_access_policies_request_model; +pub use self::people_access_policies_request_model::PeopleAccessPoliciesRequestModel; pub mod permissions; pub use self::permissions::Permissions; pub mod plan_response_model; @@ -442,12 +480,16 @@ pub mod project_access_policies_response_model; pub use self::project_access_policies_response_model::ProjectAccessPoliciesResponseModel; pub mod project_create_request_model; pub use self::project_create_request_model::ProjectCreateRequestModel; +pub mod project_people_access_policies_response_model; +pub use self::project_people_access_policies_response_model::ProjectPeopleAccessPoliciesResponseModel; pub mod project_response_model; pub use self::project_response_model::ProjectResponseModel; pub mod project_response_model_list_response_model; pub use self::project_response_model_list_response_model::ProjectResponseModelListResponseModel; pub mod project_update_request_model; pub use self::project_update_request_model::ProjectUpdateRequestModel; +pub mod protected_device_response_model; +pub use self::protected_device_response_model::ProtectedDeviceResponseModel; pub mod provider_organization_add_request_model; pub use self::provider_organization_add_request_model::ProviderOrganizationAddRequestModel; pub mod provider_organization_create_request_model; @@ -498,6 +540,12 @@ pub mod provider_user_user_details_response_model; pub use self::provider_user_user_details_response_model::ProviderUserUserDetailsResponseModel; pub mod provider_user_user_details_response_model_list_response_model; pub use self::provider_user_user_details_response_model_list_response_model::ProviderUserUserDetailsResponseModelListResponseModel; +pub mod pub_key_cred_param; +pub use self::pub_key_cred_param::PubKeyCredParam; +pub mod public_key_credential_descriptor; +pub use self::public_key_credential_descriptor::PublicKeyCredentialDescriptor; +pub mod public_key_credential_rp_entity; +pub use self::public_key_credential_rp_entity::PublicKeyCredentialRpEntity; pub mod public_key_credential_type; pub use self::public_key_credential_type::PublicKeyCredentialType; pub mod push_registration_request_model; @@ -512,6 +560,8 @@ 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 reset_password_with_org_id_request_model; +pub use self::reset_password_with_org_id_request_model::ResetPasswordWithOrgIdRequestModel; pub mod response_data; pub use self::response_data::ResponseData; pub mod revoke_access_tokens_request; @@ -536,6 +586,10 @@ pub mod secret_with_projects_inner_project; pub use self::secret_with_projects_inner_project::SecretWithProjectsInnerProject; pub mod secret_with_projects_list_response_model; pub use self::secret_with_projects_list_response_model::SecretWithProjectsListResponseModel; +pub mod secrets_manager_plan_features_response_model; +pub use self::secrets_manager_plan_features_response_model::SecretsManagerPlanFeaturesResponseModel; +pub mod secrets_manager_subscribe_request_model; +pub use self::secrets_manager_subscribe_request_model::SecretsManagerSubscribeRequestModel; pub mod secrets_manager_subscription_update_request_model; pub use self::secrets_manager_subscription_update_request_model::SecretsManagerSubscriptionUpdateRequestModel; pub mod secrets_with_projects_inner_secret; @@ -568,18 +622,20 @@ 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_policies_response_model; -pub use self::service_account_access_policies_response_model::ServiceAccountAccessPoliciesResponseModel; pub mod service_account_create_request_model; pub use self::service_account_create_request_model::ServiceAccountCreateRequestModel; +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_response_model; pub use self::service_account_project_access_policy_response_model::ServiceAccountProjectAccessPolicyResponseModel; pub mod service_account_project_access_policy_response_model_list_response_model; pub use self::service_account_project_access_policy_response_model_list_response_model::ServiceAccountProjectAccessPolicyResponseModelListResponseModel; pub mod service_account_response_model; pub use self::service_account_response_model::ServiceAccountResponseModel; -pub mod service_account_response_model_list_response_model; -pub use self::service_account_response_model_list_response_model::ServiceAccountResponseModelListResponseModel; +pub mod service_account_secrets_details_response_model; +pub use self::service_account_secrets_details_response_model::ServiceAccountSecretsDetailsResponseModel; +pub mod service_account_secrets_details_response_model_list_response_model; +pub use self::service_account_secrets_details_response_model_list_response_model::ServiceAccountSecretsDetailsResponseModelListResponseModel; pub mod service_account_update_request_model; pub use self::service_account_update_request_model::ServiceAccountUpdateRequestModel; pub mod set_key_connector_key_request_model; @@ -644,6 +700,8 @@ pub mod two_factor_yubi_key_response_model; pub use self::two_factor_yubi_key_response_model::TwoFactorYubiKeyResponseModel; pub mod update_avatar_request_model; pub use self::update_avatar_request_model::UpdateAvatarRequestModel; +pub mod update_devices_trust_request_model; +pub use self::update_devices_trust_request_model::UpdateDevicesTrustRequestModel; pub mod update_domains_request_model; pub use self::update_domains_request_model::UpdateDomainsRequestModel; pub mod update_key_request_model; @@ -672,9 +730,25 @@ 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; pub use self::verify_delete_recover_request_model::VerifyDeleteRecoverRequestModel; pub mod verify_email_request_model; pub use self::verify_email_request_model::VerifyEmailRequestModel; pub mod verify_otp_request_model; pub use self::verify_otp_request_model::VerifyOtpRequestModel; +pub mod web_authn_credential_create_options_response_model; +pub use self::web_authn_credential_create_options_response_model::WebAuthnCredentialCreateOptionsResponseModel; +pub mod web_authn_credential_response_model; +pub use self::web_authn_credential_response_model::WebAuthnCredentialResponseModel; +pub mod web_authn_credential_response_model_list_response_model; +pub use self::web_authn_credential_response_model_list_response_model::WebAuthnCredentialResponseModelListResponseModel; +pub mod web_authn_login_assertion_options_response_model; +pub use self::web_authn_login_assertion_options_response_model::WebAuthnLoginAssertionOptionsResponseModel; +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_prf_status; +pub use self::web_authn_prf_status::WebAuthnPrfStatus; diff --git a/crates/bitwarden-api-api/src/models/open_id_connect_redirect_behavior.rs b/crates/bitwarden-api-api/src/models/open_id_connect_redirect_behavior.rs index 73fb93eb8..792a2dd9e 100644 --- a/crates/bitwarden-api-api/src/models/open_id_connect_redirect_behavior.rs +++ b/crates/bitwarden-api-api/src/models/open_id_connect_redirect_behavior.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum OpenIdConnectRedirectBehavior { - Variant0 = 0, - Variant1 = 1, + RedirectGet = 0, + FormPost = 1, } impl ToString for OpenIdConnectRedirectBehavior { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::RedirectGet => String::from("0"), + Self::FormPost => String::from("1"), } } } impl Default for OpenIdConnectRedirectBehavior { fn default() -> OpenIdConnectRedirectBehavior { - Self::Variant0 + Self::RedirectGet } } diff --git a/crates/bitwarden-api-api/src/models/organization_api_key_information.rs b/crates/bitwarden-api-api/src/models/organization_api_key_information.rs index fceb50762..94c56de5f 100644 --- a/crates/bitwarden-api-api/src/models/organization_api_key_information.rs +++ b/crates/bitwarden-api-api/src/models/organization_api_key_information.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationApiKeyInformation { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_api_key_information_list_response_model.rs b/crates/bitwarden-api-api/src/models/organization_api_key_information_list_response_model.rs index 129ebf4b9..63b65454c 100644 --- a/crates/bitwarden-api-api/src/models/organization_api_key_information_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_api_key_information_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationApiKeyInformationListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_api_key_request_model.rs b/crates/bitwarden-api-api/src/models/organization_api_key_request_model.rs index 5466d6cb1..3af42cef6 100644 --- a/crates/bitwarden-api-api/src/models/organization_api_key_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_api_key_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationApiKeyRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_api_key_type.rs b/crates/bitwarden-api-api/src/models/organization_api_key_type.rs index 4790406e5..83f76baaa 100644 --- a/crates/bitwarden-api-api/src/models/organization_api_key_type.rs +++ b/crates/bitwarden-api-api/src/models/organization_api_key_type.rs @@ -14,23 +14,23 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum OrganizationApiKeyType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, + Default = 0, + BillingSync = 1, + Scim = 2, } impl ToString for OrganizationApiKeyType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::Default => String::from("0"), + Self::BillingSync => String::from("1"), + Self::Scim => String::from("2"), } } } impl Default for OrganizationApiKeyType { fn default() -> OrganizationApiKeyType { - Self::Variant0 + Self::Default } } diff --git a/crates/bitwarden-api-api/src/models/organization_auto_enroll_status_response_model.rs b/crates/bitwarden-api-api/src/models/organization_auto_enroll_status_response_model.rs index c652f55fc..e068219ee 100644 --- a/crates/bitwarden-api-api/src/models/organization_auto_enroll_status_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_auto_enroll_status_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationAutoEnrollStatusResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_collection_management_update_request_model.rs b/crates/bitwarden-api-api/src/models/organization_collection_management_update_request_model.rs new file mode 100644 index 000000000..ce5d52f6d --- /dev/null +++ b/crates/bitwarden-api-api/src/models/organization_collection_management_update_request_model.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct OrganizationCollectionManagementUpdateRequestModel { + #[serde( + rename = "limitCreateDeleteOwnerAdmin", + skip_serializing_if = "Option::is_none" + )] + pub limit_create_delete_owner_admin: Option, + #[serde( + rename = "allowAdminAccessToAllCollectionItems", + skip_serializing_if = "Option::is_none" + )] + pub allow_admin_access_to_all_collection_items: Option, +} + +impl OrganizationCollectionManagementUpdateRequestModel { + pub fn new() -> OrganizationCollectionManagementUpdateRequestModel { + OrganizationCollectionManagementUpdateRequestModel { + limit_create_delete_owner_admin: None, + allow_admin_access_to_all_collection_items: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/organization_connection_request_model.rs b/crates/bitwarden-api-api/src/models/organization_connection_request_model.rs index 3105b2b3e..d201c54b2 100644 --- a/crates/bitwarden-api-api/src/models/organization_connection_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_connection_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationConnectionRequestModel { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_connection_response_model.rs b/crates/bitwarden-api-api/src/models/organization_connection_response_model.rs index beb31068f..88ccf278c 100644 --- a/crates/bitwarden-api-api/src/models/organization_connection_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_connection_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationConnectionResponseModel { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_connection_type.rs b/crates/bitwarden-api-api/src/models/organization_connection_type.rs index eddf5460c..201c03ed2 100644 --- a/crates/bitwarden-api-api/src/models/organization_connection_type.rs +++ b/crates/bitwarden-api-api/src/models/organization_connection_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum OrganizationConnectionType { - Variant1 = 1, - Variant2 = 2, + CloudBillingSync = 1, + Scim = 2, } impl ToString for OrganizationConnectionType { fn to_string(&self) -> String { match self { - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::CloudBillingSync => String::from("1"), + Self::Scim => String::from("2"), } } } impl Default for OrganizationConnectionType { fn default() -> OrganizationConnectionType { - Self::Variant1 + Self::CloudBillingSync } } diff --git a/crates/bitwarden-api-api/src/models/organization_create_request_model.rs b/crates/bitwarden-api-api/src/models/organization_create_request_model.rs index 493737ccf..8c00b6888 100644 --- a/crates/bitwarden-api-api/src/models/organization_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationCreateRequestModel { #[serde(rename = "name")] pub name: String, @@ -77,6 +77,11 @@ pub struct OrganizationCreateRequestModel { pub additional_service_accounts: Option, #[serde(rename = "useSecretsManager")] pub use_secrets_manager: bool, + #[serde( + rename = "isFromSecretsManagerTrial", + skip_serializing_if = "Option::is_none" + )] + pub is_from_secrets_manager_trial: Option, } impl OrganizationCreateRequestModel { @@ -110,6 +115,7 @@ impl OrganizationCreateRequestModel { additional_sm_seats: None, additional_service_accounts: None, use_secrets_manager, + is_from_secrets_manager_trial: None, } } } diff --git a/crates/bitwarden-api-api/src/models/organization_domain_request_model.rs b/crates/bitwarden-api-api/src/models/organization_domain_request_model.rs index e17cfd936..b1dbe8395 100644 --- a/crates/bitwarden-api-api/src/models/organization_domain_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_domain_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationDomainRequestModel { #[serde(rename = "txt")] pub txt: String, diff --git a/crates/bitwarden-api-api/src/models/organization_domain_response_model.rs b/crates/bitwarden-api-api/src/models/organization_domain_response_model.rs index 919a18ffa..524a00f93 100644 --- a/crates/bitwarden-api-api/src/models/organization_domain_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_domain_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationDomainResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_domain_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/organization_domain_response_model_list_response_model.rs index 46db2791b..49224feb3 100644 --- a/crates/bitwarden-api-api/src/models/organization_domain_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_domain_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationDomainResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_domain_sso_details_request_model.rs b/crates/bitwarden-api-api/src/models/organization_domain_sso_details_request_model.rs index abef29d1e..a731af833 100644 --- a/crates/bitwarden-api-api/src/models/organization_domain_sso_details_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_domain_sso_details_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationDomainSsoDetailsRequestModel { #[serde(rename = "email")] pub email: String, diff --git a/crates/bitwarden-api-api/src/models/organization_domain_sso_details_response_model.rs b/crates/bitwarden-api-api/src/models/organization_domain_sso_details_response_model.rs index f9ce9db71..c409df706 100644 --- a/crates/bitwarden-api-api/src/models/organization_domain_sso_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_domain_sso_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationDomainSsoDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_enroll_secrets_manager_request_model.rs b/crates/bitwarden-api-api/src/models/organization_enroll_secrets_manager_request_model.rs deleted file mode 100644 index 16c34a3c5..000000000 --- a/crates/bitwarden-api-api/src/models/organization_enroll_secrets_manager_request_model.rs +++ /dev/null @@ -1,21 +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 - */ - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct OrganizationEnrollSecretsManagerRequestModel { - #[serde(rename = "enabled", skip_serializing_if = "Option::is_none")] - pub enabled: Option, -} - -impl OrganizationEnrollSecretsManagerRequestModel { - pub fn new() -> OrganizationEnrollSecretsManagerRequestModel { - OrganizationEnrollSecretsManagerRequestModel { enabled: None } - } -} diff --git a/crates/bitwarden-api-api/src/models/organization_keys_request_model.rs b/crates/bitwarden-api-api/src/models/organization_keys_request_model.rs index 3750a4e05..eccd5f5b3 100644 --- a/crates/bitwarden-api-api/src/models/organization_keys_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_keys_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationKeysRequestModel { #[serde(rename = "publicKey")] pub public_key: String, diff --git a/crates/bitwarden-api-api/src/models/organization_keys_response_model.rs b/crates/bitwarden-api-api/src/models/organization_keys_response_model.rs index b8bb486fb..6992abccb 100644 --- a/crates/bitwarden-api-api/src/models/organization_keys_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_keys_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationKeysResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_license.rs b/crates/bitwarden-api-api/src/models/organization_license.rs index b440b8e00..2497f073e 100644 --- a/crates/bitwarden-api-api/src/models/organization_license.rs +++ b/crates/bitwarden-api-api/src/models/organization_license.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationLicense { #[serde(rename = "licenseKey", skip_serializing_if = "Option::is_none")] pub license_key: Option, @@ -78,6 +78,24 @@ pub struct OrganizationLicense { skip_serializing_if = "Option::is_none" )] pub expiration_without_grace_period: Option, + #[serde(rename = "usePasswordManager", skip_serializing_if = "Option::is_none")] + pub use_password_manager: Option, + #[serde(rename = "useSecretsManager", skip_serializing_if = "Option::is_none")] + pub use_secrets_manager: Option, + #[serde(rename = "smSeats", skip_serializing_if = "Option::is_none")] + pub sm_seats: Option, + #[serde(rename = "smServiceAccounts", skip_serializing_if = "Option::is_none")] + pub sm_service_accounts: Option, + #[serde( + rename = "limitCollectionCreationDeletion", + skip_serializing_if = "Option::is_none" + )] + pub limit_collection_creation_deletion: Option, + #[serde( + rename = "allowAdminAccessToAllCollectionItems", + skip_serializing_if = "Option::is_none" + )] + pub allow_admin_access_to_all_collection_items: Option, #[serde(rename = "trial", skip_serializing_if = "Option::is_none")] pub trial: Option, #[serde(rename = "licenseType", skip_serializing_if = "Option::is_none")] @@ -122,6 +140,12 @@ impl OrganizationLicense { refresh: None, expires: None, expiration_without_grace_period: None, + use_password_manager: None, + use_secrets_manager: None, + sm_seats: None, + sm_service_accounts: None, + limit_collection_creation_deletion: None, + allow_admin_access_to_all_collection_items: None, trial: None, license_type: None, hash: None, diff --git a/crates/bitwarden-api-api/src/models/organization_public_key_response_model.rs b/crates/bitwarden-api-api/src/models/organization_public_key_response_model.rs new file mode 100644 index 000000000..aca53523a --- /dev/null +++ b/crates/bitwarden-api-api/src/models/organization_public_key_response_model.rs @@ -0,0 +1,26 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct OrganizationPublicKeyResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "publicKey", skip_serializing_if = "Option::is_none")] + pub public_key: Option, +} + +impl OrganizationPublicKeyResponseModel { + pub fn new() -> OrganizationPublicKeyResponseModel { + OrganizationPublicKeyResponseModel { + object: None, + public_key: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/organization_response_model.rs b/crates/bitwarden-api-api/src/models/organization_response_model.rs index 6618b1f79..e5c877fb0 100644 --- a/crates/bitwarden-api-api/src/models/organization_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -98,6 +98,21 @@ pub struct OrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub max_autoscale_sm_service_accounts: Option, + #[serde( + rename = "limitCollectionCreationDeletion", + skip_serializing_if = "Option::is_none" + )] + pub limit_collection_creation_deletion: Option, + #[serde( + rename = "allowAdminAccessToAllCollectionItems", + 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 { @@ -141,6 +156,9 @@ impl OrganizationResponseModel { sm_service_accounts: None, max_autoscale_sm_seats: None, 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_risks_subscription_failure_response_model.rs b/crates/bitwarden-api-api/src/models/organization_risks_subscription_failure_response_model.rs new file mode 100644 index 000000000..4e138cd85 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/organization_risks_subscription_failure_response_model.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct OrganizationRisksSubscriptionFailureResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "organizationId", skip_serializing_if = "Option::is_none")] + pub organization_id: Option, + #[serde( + rename = "risksSubscriptionFailure", + skip_serializing_if = "Option::is_none" + )] + pub risks_subscription_failure: Option, +} + +impl OrganizationRisksSubscriptionFailureResponseModel { + pub fn new() -> OrganizationRisksSubscriptionFailureResponseModel { + OrganizationRisksSubscriptionFailureResponseModel { + object: None, + organization_id: None, + risks_subscription_failure: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/organization_seat_request_model.rs b/crates/bitwarden-api-api/src/models/organization_seat_request_model.rs index e822b9f82..58f815f4b 100644 --- a/crates/bitwarden-api-api/src/models/organization_seat_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_seat_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSeatRequestModel { #[serde(rename = "seatAdjustment")] pub seat_adjustment: i32, diff --git a/crates/bitwarden-api-api/src/models/organization_sponsorship_create_request_model.rs b/crates/bitwarden-api-api/src/models/organization_sponsorship_create_request_model.rs index f823b56fe..dbd33d616 100644 --- a/crates/bitwarden-api-api/src/models/organization_sponsorship_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sponsorship_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSponsorshipCreateRequestModel { #[serde(rename = "planSponsorshipType")] pub plan_sponsorship_type: crate::models::PlanSponsorshipType, diff --git a/crates/bitwarden-api-api/src/models/organization_sponsorship_redeem_request_model.rs b/crates/bitwarden-api-api/src/models/organization_sponsorship_redeem_request_model.rs index b978ff27c..08a3f3741 100644 --- a/crates/bitwarden-api-api/src/models/organization_sponsorship_redeem_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sponsorship_redeem_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSponsorshipRedeemRequestModel { #[serde(rename = "planSponsorshipType")] pub plan_sponsorship_type: crate::models::PlanSponsorshipType, diff --git a/crates/bitwarden-api-api/src/models/organization_sponsorship_request_model.rs b/crates/bitwarden-api-api/src/models/organization_sponsorship_request_model.rs index 94cd9a088..247e8f3ea 100644 --- a/crates/bitwarden-api-api/src/models/organization_sponsorship_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sponsorship_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSponsorshipRequestModel { #[serde( rename = "sponsoringOrganizationUserId", diff --git a/crates/bitwarden-api-api/src/models/organization_sponsorship_response_model.rs b/crates/bitwarden-api-api/src/models/organization_sponsorship_response_model.rs index 5798d3500..21f90016a 100644 --- a/crates/bitwarden-api-api/src/models/organization_sponsorship_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sponsorship_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSponsorshipResponseModel { #[serde( rename = "sponsoringOrganizationUserId", diff --git a/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_request_model.rs b/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_request_model.rs index cfffe3ec3..3aca9f7fc 100644 --- a/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSponsorshipSyncRequestModel { #[serde(rename = "billingSyncKey", skip_serializing_if = "Option::is_none")] pub billing_sync_key: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_response_model.rs b/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_response_model.rs index 0d2b71f74..e3630d1ee 100644 --- a/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sponsorship_sync_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSponsorshipSyncResponseModel { #[serde(rename = "sponsorshipsBatch", skip_serializing_if = "Option::is_none")] pub sponsorships_batch: Option>, diff --git a/crates/bitwarden-api-api/src/models/organization_sso_request_model.rs b/crates/bitwarden-api-api/src/models/organization_sso_request_model.rs index 1accd7a2e..4818e8e5d 100644 --- a/crates/bitwarden-api-api/src/models/organization_sso_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sso_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSsoRequestModel { #[serde(rename = "enabled")] pub enabled: bool, diff --git a/crates/bitwarden-api-api/src/models/organization_sso_response_model.rs b/crates/bitwarden-api-api/src/models/organization_sso_response_model.rs index 9a1676049..d0d73e4b0 100644 --- a/crates/bitwarden-api-api/src/models/organization_sso_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_sso_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSsoResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_subscription_response_model.rs b/crates/bitwarden-api-api/src/models/organization_subscription_response_model.rs index c3789f17a..1bbfc630f 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSubscriptionResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -98,10 +98,27 @@ pub struct OrganizationSubscriptionResponseModel { skip_serializing_if = "Option::is_none" )] pub max_autoscale_sm_service_accounts: Option, + #[serde( + rename = "limitCollectionCreationDeletion", + skip_serializing_if = "Option::is_none" + )] + pub limit_collection_creation_deletion: Option, + #[serde( + rename = "allowAdminAccessToAllCollectionItems", + 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")] pub storage_gb: Option, + #[serde(rename = "customerDiscount", skip_serializing_if = "Option::is_none")] + pub customer_discount: Option>, #[serde(rename = "subscription", skip_serializing_if = "Option::is_none")] pub subscription: Option>, #[serde(rename = "upcomingInvoice", skip_serializing_if = "Option::is_none")] @@ -115,8 +132,6 @@ pub struct OrganizationSubscriptionResponseModel { /// Date when a self-hosted organization expires (includes grace period). #[serde(rename = "expiration", skip_serializing_if = "Option::is_none")] pub expiration: Option, - #[serde(rename = "secretsManagerBeta", skip_serializing_if = "Option::is_none")] - pub secrets_manager_beta: Option, } impl OrganizationSubscriptionResponseModel { @@ -160,13 +175,16 @@ impl OrganizationSubscriptionResponseModel { sm_service_accounts: None, max_autoscale_sm_seats: None, 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, subscription: None, upcoming_invoice: None, expiration_without_grace_period: None, expiration: None, - secrets_manager_beta: None, } } } diff --git a/crates/bitwarden-api-api/src/models/organization_subscription_update_request_model.rs b/crates/bitwarden-api-api/src/models/organization_subscription_update_request_model.rs index 9c919c759..d85f8d56a 100644 --- a/crates/bitwarden-api-api/src/models/organization_subscription_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_subscription_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationSubscriptionUpdateRequestModel { #[serde(rename = "seatAdjustment")] pub seat_adjustment: i32, diff --git a/crates/bitwarden-api-api/src/models/organization_tax_info_update_request_model.rs b/crates/bitwarden-api-api/src/models/organization_tax_info_update_request_model.rs index c6356037c..76a106b16 100644 --- a/crates/bitwarden-api-api/src/models/organization_tax_info_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_tax_info_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationTaxInfoUpdateRequestModel { #[serde(rename = "country")] pub country: String, diff --git a/crates/bitwarden-api-api/src/models/organization_update_request_model.rs b/crates/bitwarden-api-api/src/models/organization_update_request_model.rs index f4603d6a0..ede9d668a 100644 --- a/crates/bitwarden-api-api/src/models/organization_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUpdateRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/organization_upgrade_request_model.rs b/crates/bitwarden-api-api/src/models/organization_upgrade_request_model.rs index 23f605dab..45bc99dd7 100644 --- a/crates/bitwarden-api-api/src/models/organization_upgrade_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_upgrade_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUpgradeRequestModel { #[serde(rename = "businessName", skip_serializing_if = "Option::is_none")] pub business_name: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_user_accept_init_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_accept_init_request_model.rs index 3ef9d7ced..ab4f7f0ff 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_accept_init_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_accept_init_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserAcceptInitRequestModel { #[serde(rename = "token")] pub token: String, diff --git a/crates/bitwarden-api-api/src/models/organization_user_accept_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_accept_request_model.rs index b46fc5851..62732f1d3 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_accept_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_accept_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserAcceptRequestModel { #[serde(rename = "token")] pub token: String, diff --git a/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model.rs index 7530bb3a7..468d654a3 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserBulkConfirmRequestModel { #[serde(rename = "keys")] pub keys: Vec, diff --git a/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model_entry.rs b/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model_entry.rs index a349578fa..f3ccb5e08 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model_entry.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_bulk_confirm_request_model_entry.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserBulkConfirmRequestModelEntry { #[serde(rename = "id")] pub id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/models/organization_user_bulk_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_bulk_request_model.rs index 14fdaa561..6f973f960 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_bulk_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_bulk_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserBulkRequestModel { #[serde(rename = "ids")] pub ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model.rs index ba66e6f1f..f84f1509d 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserBulkResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model_list_response_model.rs index 5adc0087e..0be2de9a1 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_bulk_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserBulkResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_user_confirm_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_confirm_request_model.rs index f5abe9011..e6a00ae6b 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_confirm_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_confirm_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserConfirmRequestModel { #[serde(rename = "key")] pub key: String, 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 179bb4a54..5fe8caf42 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 68de06fe1..0cf5c3429 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserInviteRequestModel { #[serde(rename = "emails")] pub emails: Vec, diff --git a/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model.rs index b3dbf5991..5bb053f81 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserPublicKeyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model_list_response_model.rs index d6de32d79..ae59cc365 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_public_key_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserPublicKeyResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 0dbd87182..07343bf5b 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserResetPasswordDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_user_reset_password_enrollment_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_reset_password_enrollment_request_model.rs index c21020f96..c11fcda48 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 @@ -8,19 +8,8 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserResetPasswordEnrollmentRequestModel { - #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] - pub master_password_hash: Option, - #[serde(rename = "otp", skip_serializing_if = "Option::is_none")] - pub otp: Option, - #[serde( - rename = "authRequestAccessCode", - skip_serializing_if = "Option::is_none" - )] - pub auth_request_access_code: Option, - #[serde(rename = "secret", skip_serializing_if = "Option::is_none")] - pub secret: Option, #[serde(rename = "resetPasswordKey", skip_serializing_if = "Option::is_none")] pub reset_password_key: Option, } @@ -28,10 +17,6 @@ pub struct OrganizationUserResetPasswordEnrollmentRequestModel { impl OrganizationUserResetPasswordEnrollmentRequestModel { pub fn new() -> OrganizationUserResetPasswordEnrollmentRequestModel { OrganizationUserResetPasswordEnrollmentRequestModel { - master_password_hash: None, - otp: None, - auth_request_access_code: None, - secret: None, reset_password_key: None, } } diff --git a/crates/bitwarden-api-api/src/models/organization_user_reset_password_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_reset_password_request_model.rs index 210aa2ec9..001086531 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_reset_password_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_reset_password_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserResetPasswordRequestModel { #[serde(rename = "newMasterPasswordHash")] pub new_master_password_hash: String, diff --git a/crates/bitwarden-api-api/src/models/organization_user_status_type.rs b/crates/bitwarden-api-api/src/models/organization_user_status_type.rs index 7486cab57..5d3c99199 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_status_type.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_status_type.rs @@ -14,25 +14,25 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum OrganizationUserStatusType { - _0 = 0, - _1 = 1, - _2 = 2, - __1 = -1, + Invited = 0, + Accepted = 1, + Confirmed = 2, + Revoked = -1, } impl ToString for OrganizationUserStatusType { fn to_string(&self) -> String { match self { - Self::_0 => String::from("0"), - Self::_1 => String::from("1"), - Self::_2 => String::from("2"), - Self::__1 => String::from("-1"), + Self::Invited => String::from("0"), + Self::Accepted => String::from("1"), + Self::Confirmed => String::from("2"), + Self::Revoked => String::from("-1"), } } } impl Default for OrganizationUserStatusType { fn default() -> OrganizationUserStatusType { - Self::_0 + Self::Invited } } 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 e3642946d..38e415f05 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_type.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_type.rs @@ -14,27 +14,27 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum OrganizationUserType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, + Owner = 0, + Admin = 1, + User = 2, + Manager = 3, + Custom = 4, } impl ToString for OrganizationUserType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), + Self::Owner => String::from("0"), + Self::Admin => String::from("1"), + Self::User => String::from("2"), + Self::Manager => String::from("3"), + Self::Custom => String::from("4"), } } } impl Default for OrganizationUserType { fn default() -> OrganizationUserType { - Self::Variant0 + Self::Owner } } diff --git a/crates/bitwarden-api-api/src/models/organization_user_update_groups_request_model.rs b/crates/bitwarden-api-api/src/models/organization_user_update_groups_request_model.rs index 38c9dafe7..c9b636fc9 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_update_groups_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_update_groups_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserUpdateGroupsRequestModel { #[serde(rename = "groupIds")] pub group_ids: Vec, 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 1d83589ee..54b479dd6 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserUpdateRequestModel { #[serde(rename = "type")] pub r#type: crate::models::OrganizationUserType, 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 96d06ff30..add9e86e4 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserUserDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model_list_response_model.rs index f0975fc2d..e3feac1db 100644 --- a/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_user_user_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationUserUserDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/organization_verify_bank_request_model.rs b/crates/bitwarden-api-api/src/models/organization_verify_bank_request_model.rs index f46a657a0..e19bdb89d 100644 --- a/crates/bitwarden-api-api/src/models/organization_verify_bank_request_model.rs +++ b/crates/bitwarden-api-api/src/models/organization_verify_bank_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OrganizationVerifyBankRequestModel { #[serde(rename = "amount1")] pub amount1: i32, diff --git a/crates/bitwarden-api-api/src/models/other_device_keys_update_request_model.rs b/crates/bitwarden-api-api/src/models/other_device_keys_update_request_model.rs new file mode 100644 index 000000000..d7454c7cf --- /dev/null +++ b/crates/bitwarden-api-api/src/models/other_device_keys_update_request_model.rs @@ -0,0 +1,33 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct OtherDeviceKeysUpdateRequestModel { + #[serde(rename = "encryptedPublicKey")] + pub encrypted_public_key: String, + #[serde(rename = "encryptedUserKey")] + pub encrypted_user_key: String, + #[serde(rename = "deviceId")] + pub device_id: uuid::Uuid, +} + +impl OtherDeviceKeysUpdateRequestModel { + pub fn new( + encrypted_public_key: String, + encrypted_user_key: String, + device_id: uuid::Uuid, + ) -> OtherDeviceKeysUpdateRequestModel { + OtherDeviceKeysUpdateRequestModel { + encrypted_public_key, + encrypted_user_key, + device_id, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/password_hint_request_model.rs b/crates/bitwarden-api-api/src/models/password_hint_request_model.rs index 6e4692bf2..8704c6fe1 100644 --- a/crates/bitwarden-api-api/src/models/password_hint_request_model.rs +++ b/crates/bitwarden-api-api/src/models/password_hint_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PasswordHintRequestModel { #[serde(rename = "email")] pub email: String, 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 new file mode 100644 index 000000000..91fa49b6a --- /dev/null +++ b/crates/bitwarden-api-api/src/models/password_manager_plan_features_response_model.rs @@ -0,0 +1,98 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PasswordManagerPlanFeaturesResponseModel { + #[serde(rename = "stripePlanId", skip_serializing_if = "Option::is_none")] + pub stripe_plan_id: Option, + #[serde(rename = "stripeSeatPlanId", skip_serializing_if = "Option::is_none")] + pub stripe_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 = "allowSeatAutoscale", skip_serializing_if = "Option::is_none")] + pub allow_seat_autoscale: Option, + #[serde( + rename = "hasAdditionalSeatsOption", + skip_serializing_if = "Option::is_none" + )] + pub has_additional_seats_option: Option, + #[serde(rename = "maxAdditionalSeats", skip_serializing_if = "Option::is_none")] + pub max_additional_seats: Option, + #[serde(rename = "baseSeats", skip_serializing_if = "Option::is_none")] + pub base_seats: Option, + #[serde( + rename = "hasPremiumAccessOption", + skip_serializing_if = "Option::is_none" + )] + pub has_premium_access_option: Option, + #[serde( + rename = "stripePremiumAccessPlanId", + skip_serializing_if = "Option::is_none" + )] + pub stripe_premium_access_plan_id: Option, + #[serde( + rename = "premiumAccessOptionPrice", + skip_serializing_if = "Option::is_none" + )] + pub premium_access_option_price: Option, + #[serde(rename = "maxSeats", skip_serializing_if = "Option::is_none")] + pub max_seats: Option, + #[serde(rename = "baseStorageGb", skip_serializing_if = "Option::is_none")] + pub base_storage_gb: Option, + #[serde( + rename = "hasAdditionalStorageOption", + skip_serializing_if = "Option::is_none" + )] + pub has_additional_storage_option: Option, + #[serde( + rename = "additionalStoragePricePerGb", + skip_serializing_if = "Option::is_none" + )] + pub additional_storage_price_per_gb: Option, + #[serde( + rename = "stripeStoragePlanId", + skip_serializing_if = "Option::is_none" + )] + pub stripe_storage_plan_id: Option, + #[serde( + rename = "maxAdditionalStorage", + skip_serializing_if = "Option::is_none" + )] + pub max_additional_storage: Option, + #[serde(rename = "maxCollections", skip_serializing_if = "Option::is_none")] + pub max_collections: Option, +} + +impl PasswordManagerPlanFeaturesResponseModel { + pub fn new() -> PasswordManagerPlanFeaturesResponseModel { + PasswordManagerPlanFeaturesResponseModel { + stripe_plan_id: None, + stripe_seat_plan_id: None, + base_price: None, + seat_price: None, + allow_seat_autoscale: None, + has_additional_seats_option: None, + max_additional_seats: None, + base_seats: None, + has_premium_access_option: None, + stripe_premium_access_plan_id: None, + premium_access_option_price: None, + max_seats: None, + base_storage_gb: None, + has_additional_storage_option: None, + additional_storage_price_per_gb: None, + stripe_storage_plan_id: None, + max_additional_storage: None, + max_collections: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/password_request_model.rs b/crates/bitwarden-api-api/src/models/password_request_model.rs index a1cac4dd2..c7fcb6e8b 100644 --- a/crates/bitwarden-api-api/src/models/password_request_model.rs +++ b/crates/bitwarden-api-api/src/models/password_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PasswordRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/payment_method_type.rs b/crates/bitwarden-api-api/src/models/payment_method_type.rs index 2b3cbd1f0..c4d6cf93c 100644 --- a/crates/bitwarden-api-api/src/models/payment_method_type.rs +++ b/crates/bitwarden-api-api/src/models/payment_method_type.rs @@ -14,37 +14,33 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum PaymentMethodType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, - Variant8 = 8, - Variant255 = 255, + Card = 0, + BankAccount = 1, + PayPal = 2, + BitPay = 3, + Credit = 4, + WireTransfer = 5, + Check = 8, + None = 255, } impl ToString for PaymentMethodType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), - Self::Variant8 => String::from("8"), - Self::Variant255 => String::from("255"), + Self::Card => String::from("0"), + Self::BankAccount => String::from("1"), + Self::PayPal => String::from("2"), + Self::BitPay => String::from("3"), + Self::Credit => String::from("4"), + Self::WireTransfer => String::from("5"), + Self::Check => String::from("8"), + Self::None => String::from("255"), } } } impl Default for PaymentMethodType { fn default() -> PaymentMethodType { - Self::Variant0 + Self::Card } } diff --git a/crates/bitwarden-api-api/src/models/payment_request_model.rs b/crates/bitwarden-api-api/src/models/payment_request_model.rs index 08cb166cf..00e07ff11 100644 --- a/crates/bitwarden-api-api/src/models/payment_request_model.rs +++ b/crates/bitwarden-api-api/src/models/payment_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PaymentRequestModel { #[serde(rename = "country")] pub country: String, diff --git a/crates/bitwarden-api-api/src/models/payment_response_model.rs b/crates/bitwarden-api-api/src/models/payment_response_model.rs index 696b5ad10..2eddb498f 100644 --- a/crates/bitwarden-api-api/src/models/payment_response_model.rs +++ b/crates/bitwarden-api-api/src/models/payment_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PaymentResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model.rs b/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model.rs index fafb94836..5a0bc7bcb 100644 --- a/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model.rs +++ b/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PendingOrganizationAuthRequestResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model_list_response_model.rs index 43b6a9d31..d97c8c4ba 100644 --- a/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/pending_organization_auth_request_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PendingOrganizationAuthRequestResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/people_access_policies_request_model.rs b/crates/bitwarden-api-api/src/models/people_access_policies_request_model.rs new file mode 100644 index 000000000..909ce9c30 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/people_access_policies_request_model.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PeopleAccessPoliciesRequestModel { + #[serde( + rename = "userAccessPolicyRequests", + skip_serializing_if = "Option::is_none" + )] + pub user_access_policy_requests: Option>, + #[serde( + rename = "groupAccessPolicyRequests", + skip_serializing_if = "Option::is_none" + )] + pub group_access_policy_requests: Option>, +} + +impl PeopleAccessPoliciesRequestModel { + pub fn new() -> PeopleAccessPoliciesRequestModel { + PeopleAccessPoliciesRequestModel { + user_access_policy_requests: None, + group_access_policy_requests: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/permissions.rs b/crates/bitwarden-api-api/src/models/permissions.rs index d879faddc..3d3a6b7b7 100644 --- a/crates/bitwarden-api-api/src/models/permissions.rs +++ b/crates/bitwarden-api-api/src/models/permissions.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Permissions { #[serde(rename = "accessEventLogs", skip_serializing_if = "Option::is_none")] pub access_event_logs: Option, 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 17dcb777b..e1e264e9a 100644 --- a/crates/bitwarden-api-api/src/models/plan_response_model.rs +++ b/crates/bitwarden-api-api/src/models/plan_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PlanResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -35,36 +35,6 @@ pub struct PlanResponseModel { skip_serializing_if = "Option::is_none" )] pub can_be_used_by_business: Option, - #[serde(rename = "baseSeats", skip_serializing_if = "Option::is_none")] - pub base_seats: Option, - #[serde(rename = "baseStorageGb", skip_serializing_if = "Option::is_none")] - pub base_storage_gb: Option, - #[serde(rename = "maxCollections", skip_serializing_if = "Option::is_none")] - pub max_collections: Option, - #[serde(rename = "maxUsers", skip_serializing_if = "Option::is_none")] - pub max_users: Option, - #[serde( - rename = "hasAdditionalSeatsOption", - skip_serializing_if = "Option::is_none" - )] - pub has_additional_seats_option: Option, - #[serde(rename = "maxAdditionalSeats", skip_serializing_if = "Option::is_none")] - pub max_additional_seats: Option, - #[serde( - rename = "hasAdditionalStorageOption", - skip_serializing_if = "Option::is_none" - )] - pub has_additional_storage_option: Option, - #[serde( - rename = "maxAdditionalStorage", - skip_serializing_if = "Option::is_none" - )] - pub max_additional_storage: Option, - #[serde( - rename = "hasPremiumAccessOption", - skip_serializing_if = "Option::is_none" - )] - pub has_premium_access_option: Option, #[serde(rename = "trialPeriodDays", skip_serializing_if = "Option::is_none")] pub trial_period_days: Option, #[serde(rename = "hasSelfHost", skip_serializing_if = "Option::is_none")] @@ -97,62 +67,10 @@ pub struct PlanResponseModel { pub legacy_year: Option, #[serde(rename = "disabled", skip_serializing_if = "Option::is_none")] pub disabled: Option, - #[serde(rename = "stripePlanId", skip_serializing_if = "Option::is_none")] - pub stripe_plan_id: Option, - #[serde(rename = "stripeSeatPlanId", skip_serializing_if = "Option::is_none")] - pub stripe_seat_plan_id: Option, - #[serde( - rename = "stripeStoragePlanId", - skip_serializing_if = "Option::is_none" - )] - pub stripe_storage_plan_id: Option, - #[serde( - rename = "stripePremiumAccessPlanId", - skip_serializing_if = "Option::is_none" - )] - pub stripe_premium_access_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 = "additionalStoragePricePerGb", - skip_serializing_if = "Option::is_none" - )] - pub additional_storage_price_per_gb: Option, - #[serde( - rename = "premiumAccessOptionPrice", - skip_serializing_if = "Option::is_none" - )] - pub premium_access_option_price: Option, - #[serde( - rename = "stripeServiceAccountPlanId", - skip_serializing_if = "Option::is_none" - )] - pub stripe_service_account_plan_id: Option, - #[serde( - rename = "additionalPricePerServiceAccount", - skip_serializing_if = "Option::is_none" - )] - pub additional_price_per_service_account: Option, - #[serde(rename = "baseServiceAccount", skip_serializing_if = "Option::is_none")] - pub base_service_account: Option, - #[serde(rename = "maxServiceAccounts", skip_serializing_if = "Option::is_none")] - pub max_service_accounts: Option, - #[serde( - rename = "maxAdditionalServiceAccounts", - skip_serializing_if = "Option::is_none" - )] - pub max_additional_service_accounts: Option, - #[serde( - rename = "hasAdditionalServiceAccountOption", - skip_serializing_if = "Option::is_none" - )] - pub has_additional_service_account_option: Option, - #[serde(rename = "maxProjects", skip_serializing_if = "Option::is_none")] - pub max_projects: Option, - #[serde(rename = "bitwardenProduct", skip_serializing_if = "Option::is_none")] - pub bitwarden_product: Option, + #[serde(rename = "secretsManager", skip_serializing_if = "Option::is_none")] + pub secrets_manager: Option>, + #[serde(rename = "passwordManager", skip_serializing_if = "Option::is_none")] + pub password_manager: Option>, } impl PlanResponseModel { @@ -166,15 +84,6 @@ impl PlanResponseModel { name_localization_key: None, description_localization_key: None, can_be_used_by_business: None, - base_seats: None, - base_storage_gb: None, - max_collections: None, - max_users: None, - has_additional_seats_option: None, - max_additional_seats: None, - has_additional_storage_option: None, - max_additional_storage: None, - has_premium_access_option: None, trial_period_days: None, has_self_host: None, has_policies: None, @@ -191,22 +100,8 @@ impl PlanResponseModel { display_sort_order: None, legacy_year: None, disabled: None, - stripe_plan_id: None, - stripe_seat_plan_id: None, - stripe_storage_plan_id: None, - stripe_premium_access_plan_id: None, - base_price: None, - seat_price: None, - additional_storage_price_per_gb: None, - premium_access_option_price: None, - stripe_service_account_plan_id: None, - additional_price_per_service_account: None, - base_service_account: None, - max_service_accounts: None, - max_additional_service_accounts: None, - has_additional_service_account_option: None, - max_projects: None, - bitwarden_product: None, + secrets_manager: None, + password_manager: None, } } } diff --git a/crates/bitwarden-api-api/src/models/plan_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/plan_response_model_list_response_model.rs index afbb0b849..15ce8c37a 100644 --- a/crates/bitwarden-api-api/src/models/plan_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/plan_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PlanResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/plan_sponsorship_type.rs b/crates/bitwarden-api-api/src/models/plan_sponsorship_type.rs index 5e2a55824..02a750f3d 100644 --- a/crates/bitwarden-api-api/src/models/plan_sponsorship_type.rs +++ b/crates/bitwarden-api-api/src/models/plan_sponsorship_type.rs @@ -14,19 +14,19 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum PlanSponsorshipType { - Variant0 = 0, + FamiliesForEnterprise = 0, } impl ToString for PlanSponsorshipType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), + Self::FamiliesForEnterprise => String::from("0"), } } } impl Default for PlanSponsorshipType { fn default() -> PlanSponsorshipType { - Self::Variant0 + Self::FamiliesForEnterprise } } diff --git a/crates/bitwarden-api-api/src/models/plan_type.rs b/crates/bitwarden-api-api/src/models/plan_type.rs index 62d713894..109e9250e 100644 --- a/crates/bitwarden-api-api/src/models/plan_type.rs +++ b/crates/bitwarden-api-api/src/models/plan_type.rs @@ -14,41 +14,51 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum PlanType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, - Variant8 = 8, - Variant9 = 9, - Variant10 = 10, - Variant11 = 11, + Free = 0, + FamiliesAnnually2019 = 1, + TeamsMonthly2019 = 2, + TeamsAnnually2019 = 3, + EnterpriseMonthly2019 = 4, + EnterpriseAnnually2019 = 5, + Custom = 6, + FamiliesAnnually = 7, + TeamsMonthly2020 = 8, + TeamsAnnually2020 = 9, + EnterpriseMonthly2020 = 10, + EnterpriseAnnually2020 = 11, + TeamsMonthly = 12, + TeamsAnnually = 13, + EnterpriseMonthly = 14, + EnterpriseAnnually = 15, + TeamsStarter = 16, } impl ToString for PlanType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), - Self::Variant8 => String::from("8"), - Self::Variant9 => String::from("9"), - Self::Variant10 => String::from("10"), - Self::Variant11 => String::from("11"), + Self::Free => String::from("0"), + Self::FamiliesAnnually2019 => String::from("1"), + Self::TeamsMonthly2019 => String::from("2"), + Self::TeamsAnnually2019 => String::from("3"), + Self::EnterpriseMonthly2019 => String::from("4"), + Self::EnterpriseAnnually2019 => String::from("5"), + Self::Custom => String::from("6"), + Self::FamiliesAnnually => String::from("7"), + Self::TeamsMonthly2020 => String::from("8"), + Self::TeamsAnnually2020 => String::from("9"), + Self::EnterpriseMonthly2020 => String::from("10"), + Self::EnterpriseAnnually2020 => String::from("11"), + Self::TeamsMonthly => String::from("12"), + Self::TeamsAnnually => String::from("13"), + Self::EnterpriseMonthly => String::from("14"), + Self::EnterpriseAnnually => String::from("15"), + Self::TeamsStarter => String::from("16"), } } } impl Default for PlanType { fn default() -> PlanType { - Self::Variant0 + Self::Free } } diff --git a/crates/bitwarden-api-api/src/models/policy_request_model.rs b/crates/bitwarden-api-api/src/models/policy_request_model.rs index 509b98c83..31b3880ea 100644 --- a/crates/bitwarden-api-api/src/models/policy_request_model.rs +++ b/crates/bitwarden-api-api/src/models/policy_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PolicyRequestModel { #[serde(rename = "type")] pub r#type: crate::models::PolicyType, diff --git a/crates/bitwarden-api-api/src/models/policy_response_model.rs b/crates/bitwarden-api-api/src/models/policy_response_model.rs index 4c5d4a633..bda9e87a5 100644 --- a/crates/bitwarden-api-api/src/models/policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/policy_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/policy_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/policy_response_model_list_response_model.rs index 82ee4f996..bc96d434a 100644 --- a/crates/bitwarden-api-api/src/models/policy_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/policy_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PolicyResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/policy_type.rs b/crates/bitwarden-api-api/src/models/policy_type.rs index dbb186a21..fc9e6ca63 100644 --- a/crates/bitwarden-api-api/src/models/policy_type.rs +++ b/crates/bitwarden-api-api/src/models/policy_type.rs @@ -14,41 +14,41 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum PolicyType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, - Variant8 = 8, - Variant9 = 9, - Variant10 = 10, - Variant11 = 11, + TwoFactorAuthentication = 0, + MasterPassword = 1, + PasswordGenerator = 2, + SingleOrg = 3, + RequireSso = 4, + PersonalOwnership = 5, + DisableSend = 6, + SendOptions = 7, + ResetPassword = 8, + MaximumVaultTimeout = 9, + DisablePersonalVaultExport = 10, + ActivateAutofill = 11, } impl ToString for PolicyType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), - Self::Variant8 => String::from("8"), - Self::Variant9 => String::from("9"), - Self::Variant10 => String::from("10"), - Self::Variant11 => String::from("11"), + Self::TwoFactorAuthentication => String::from("0"), + Self::MasterPassword => String::from("1"), + Self::PasswordGenerator => String::from("2"), + Self::SingleOrg => String::from("3"), + Self::RequireSso => String::from("4"), + Self::PersonalOwnership => String::from("5"), + Self::DisableSend => String::from("6"), + Self::SendOptions => String::from("7"), + Self::ResetPassword => String::from("8"), + Self::MaximumVaultTimeout => String::from("9"), + Self::DisablePersonalVaultExport => String::from("10"), + Self::ActivateAutofill => String::from("11"), } } } impl Default for PolicyType { fn default() -> PolicyType { - Self::Variant0 + Self::TwoFactorAuthentication } } diff --git a/crates/bitwarden-api-api/src/models/potential_grantee_response_model.rs b/crates/bitwarden-api-api/src/models/potential_grantee_response_model.rs index 895a48705..52adb76f6 100644 --- a/crates/bitwarden-api-api/src/models/potential_grantee_response_model.rs +++ b/crates/bitwarden-api-api/src/models/potential_grantee_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PotentialGranteeResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -20,6 +20,10 @@ pub struct PotentialGranteeResponseModel { pub r#type: Option, #[serde(rename = "email", skip_serializing_if = "Option::is_none")] pub email: Option, + #[serde(rename = "currentUserInGroup", skip_serializing_if = "Option::is_none")] + pub current_user_in_group: Option, + #[serde(rename = "currentUser", skip_serializing_if = "Option::is_none")] + pub current_user: Option, } impl PotentialGranteeResponseModel { @@ -30,6 +34,8 @@ impl PotentialGranteeResponseModel { name: None, r#type: None, email: None, + current_user_in_group: None, + current_user: None, } } } diff --git a/crates/bitwarden-api-api/src/models/potential_grantee_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/potential_grantee_response_model_list_response_model.rs index acbe8f4bb..65da6ae07 100644 --- a/crates/bitwarden-api-api/src/models/potential_grantee_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/potential_grantee_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PotentialGranteeResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/prelogin_request_model.rs b/crates/bitwarden-api-api/src/models/prelogin_request_model.rs index 4749b2abd..855c9fb09 100644 --- a/crates/bitwarden-api-api/src/models/prelogin_request_model.rs +++ b/crates/bitwarden-api-api/src/models/prelogin_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PreloginRequestModel { #[serde(rename = "email")] pub email: String, diff --git a/crates/bitwarden-api-api/src/models/prelogin_response_model.rs b/crates/bitwarden-api-api/src/models/prelogin_response_model.rs index 2b4589486..bf5d536d2 100644 --- a/crates/bitwarden-api-api/src/models/prelogin_response_model.rs +++ b/crates/bitwarden-api-api/src/models/prelogin_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PreloginResponseModel { #[serde(rename = "kdf", skip_serializing_if = "Option::is_none")] pub kdf: Option, diff --git a/crates/bitwarden-api-api/src/models/product_type.rs b/crates/bitwarden-api-api/src/models/product_type.rs index dd4882690..e60a9c57c 100644 --- a/crates/bitwarden-api-api/src/models/product_type.rs +++ b/crates/bitwarden-api-api/src/models/product_type.rs @@ -14,25 +14,27 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum ProductType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, + Free = 0, + Families = 1, + Teams = 2, + Enterprise = 3, + TeamsStarter = 4, } impl ToString for ProductType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), + Self::Free => String::from("0"), + Self::Families => String::from("1"), + Self::Teams => String::from("2"), + Self::Enterprise => String::from("3"), + Self::TeamsStarter => String::from("4"), } } } impl Default for ProductType { fn default() -> ProductType { - Self::Variant0 + 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 ec818fb7d..5984d13ae 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProfileOrganizationResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -133,6 +133,21 @@ pub struct ProfileOrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub access_secrets_manager: Option, + #[serde( + rename = "limitCollectionCreationDeletion", + skip_serializing_if = "Option::is_none" + )] + pub limit_collection_creation_deletion: Option, + #[serde( + rename = "allowAdminAccessToAllCollectionItems", + 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 { @@ -183,6 +198,9 @@ impl ProfileOrganizationResponseModel { family_sponsorship_valid_until: None, family_sponsorship_to_delete: None, 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_organization_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/profile_organization_response_model_list_response_model.rs index e794446dd..c1fb06209 100644 --- a/crates/bitwarden-api-api/src/models/profile_organization_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/profile_organization_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProfileOrganizationResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 9f8a03ba9..af8b13fb6 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProfileProviderOrganizationResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -133,6 +133,21 @@ pub struct ProfileProviderOrganizationResponseModel { skip_serializing_if = "Option::is_none" )] pub access_secrets_manager: Option, + #[serde( + rename = "limitCollectionCreationDeletion", + skip_serializing_if = "Option::is_none" + )] + pub limit_collection_creation_deletion: Option, + #[serde( + rename = "allowAdminAccessToAllCollectionItems", + 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 { @@ -183,6 +198,9 @@ impl ProfileProviderOrganizationResponseModel { family_sponsorship_valid_until: None, family_sponsorship_to_delete: None, 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_response_model.rs b/crates/bitwarden-api-api/src/models/profile_provider_response_model.rs index 06426a78b..f9a5cd392 100644 --- a/crates/bitwarden-api-api/src/models/profile_provider_response_model.rs +++ b/crates/bitwarden-api-api/src/models/profile_provider_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProfileProviderResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/profile_response_model.rs b/crates/bitwarden-api-api/src/models/profile_response_model.rs index 7ccf71a1f..a151875b1 100644 --- a/crates/bitwarden-api-api/src/models/profile_response_model.rs +++ b/crates/bitwarden-api-api/src/models/profile_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProfileResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -45,6 +45,8 @@ pub struct ProfileResponseModel { pub uses_key_connector: Option, #[serde(rename = "avatarColor", skip_serializing_if = "Option::is_none")] pub avatar_color: Option, + #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] + pub creation_date: Option, #[serde(rename = "organizations", skip_serializing_if = "Option::is_none")] pub organizations: Option>, #[serde(rename = "providers", skip_serializing_if = "Option::is_none")] @@ -76,6 +78,7 @@ impl ProfileResponseModel { force_password_reset: None, uses_key_connector: None, avatar_color: None, + creation_date: None, organizations: None, providers: None, provider_organizations: None, diff --git a/crates/bitwarden-api-api/src/models/project_access_policies_response_model.rs b/crates/bitwarden-api-api/src/models/project_access_policies_response_model.rs index 9bb4bc4d8..17daba3d9 100644 --- a/crates/bitwarden-api-api/src/models/project_access_policies_response_model.rs +++ b/crates/bitwarden-api-api/src/models/project_access_policies_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProjectAccessPoliciesResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/project_create_request_model.rs b/crates/bitwarden-api-api/src/models/project_create_request_model.rs index a74931e1f..dcec98f62 100644 --- a/crates/bitwarden-api-api/src/models/project_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/project_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProjectCreateRequestModel { #[serde(rename = "name")] pub name: String, 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 new file mode 100644 index 000000000..27655433f --- /dev/null +++ b/crates/bitwarden-api-api/src/models/project_people_access_policies_response_model.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +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>, + #[serde( + rename = "groupAccessPolicies", + skip_serializing_if = "Option::is_none" + )] + pub group_access_policies: Option>, +} + +impl ProjectPeopleAccessPoliciesResponseModel { + pub fn new() -> ProjectPeopleAccessPoliciesResponseModel { + ProjectPeopleAccessPoliciesResponseModel { + object: None, + user_access_policies: None, + group_access_policies: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/project_response_model.rs b/crates/bitwarden-api-api/src/models/project_response_model.rs index a6045885d..93bc6f14e 100644 --- a/crates/bitwarden-api-api/src/models/project_response_model.rs +++ b/crates/bitwarden-api-api/src/models/project_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProjectResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/project_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/project_response_model_list_response_model.rs index 4978edbe6..b6143ddf5 100644 --- a/crates/bitwarden-api-api/src/models/project_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/project_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProjectResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/project_update_request_model.rs b/crates/bitwarden-api-api/src/models/project_update_request_model.rs index a0c85cdf8..913168899 100644 --- a/crates/bitwarden-api-api/src/models/project_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/project_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProjectUpdateRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/protected_device_response_model.rs b/crates/bitwarden-api-api/src/models/protected_device_response_model.rs new file mode 100644 index 000000000..055d34c20 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/protected_device_response_model.rs @@ -0,0 +1,44 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ProtectedDeviceResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub r#type: Option, + #[serde(rename = "identifier", skip_serializing_if = "Option::is_none")] + pub identifier: Option, + #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] + pub creation_date: Option, + #[serde(rename = "encryptedUserKey", skip_serializing_if = "Option::is_none")] + pub encrypted_user_key: Option, + #[serde(rename = "encryptedPublicKey", skip_serializing_if = "Option::is_none")] + pub encrypted_public_key: Option, +} + +impl ProtectedDeviceResponseModel { + pub fn new() -> ProtectedDeviceResponseModel { + ProtectedDeviceResponseModel { + object: None, + id: None, + name: None, + r#type: None, + identifier: None, + creation_date: None, + encrypted_user_key: None, + encrypted_public_key: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/provider_organization_add_request_model.rs b/crates/bitwarden-api-api/src/models/provider_organization_add_request_model.rs index 2c8c66697..c25569d90 100644 --- a/crates/bitwarden-api-api/src/models/provider_organization_add_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_organization_add_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderOrganizationAddRequestModel { #[serde(rename = "organizationId")] pub organization_id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/models/provider_organization_create_request_model.rs b/crates/bitwarden-api-api/src/models/provider_organization_create_request_model.rs index 69e5c63d6..4cfb59343 100644 --- a/crates/bitwarden-api-api/src/models/provider_organization_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_organization_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderOrganizationCreateRequestModel { #[serde(rename = "clientOwnerEmail")] pub client_owner_email: String, 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 bb4e3a029..2c8e99a99 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderOrganizationOrganizationDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model_list_response_model.rs index 6e4671153..114240fc1 100644 --- a/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_organization_organization_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderOrganizationOrganizationDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 c72f7a9bf..a1d339190 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderOrganizationResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_response_model.rs b/crates/bitwarden-api-api/src/models/provider_response_model.rs index 2ca58e08c..83cfe0b69 100644 --- a/crates/bitwarden-api-api/src/models/provider_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -30,6 +30,8 @@ pub struct ProviderResponseModel { pub business_tax_number: Option, #[serde(rename = "billingEmail", skip_serializing_if = "Option::is_none")] pub billing_email: Option, + #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] + pub creation_date: Option, } impl ProviderResponseModel { @@ -45,6 +47,7 @@ impl ProviderResponseModel { business_country: None, business_tax_number: None, billing_email: None, + creation_date: None, } } } diff --git a/crates/bitwarden-api-api/src/models/provider_setup_request_model.rs b/crates/bitwarden-api-api/src/models/provider_setup_request_model.rs index 5ffdc9a6e..dacc39a8d 100644 --- a/crates/bitwarden-api-api/src/models/provider_setup_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_setup_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderSetupRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/provider_type.rs b/crates/bitwarden-api-api/src/models/provider_type.rs index 340210d32..f2d30a6cd 100644 --- a/crates/bitwarden-api-api/src/models/provider_type.rs +++ b/crates/bitwarden-api-api/src/models/provider_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum ProviderType { - Variant0 = 0, - Variant1 = 1, + Msp = 0, + Reseller = 1, } impl ToString for ProviderType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::Msp => String::from("0"), + Self::Reseller => String::from("1"), } } } impl Default for ProviderType { fn default() -> ProviderType { - Self::Variant0 + Self::Msp } } diff --git a/crates/bitwarden-api-api/src/models/provider_update_request_model.rs b/crates/bitwarden-api-api/src/models/provider_update_request_model.rs index bf03dfc66..39d892904 100644 --- a/crates/bitwarden-api-api/src/models/provider_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUpdateRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/provider_user_accept_request_model.rs b/crates/bitwarden-api-api/src/models/provider_user_accept_request_model.rs index 3f037c22b..cf526fa7c 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_accept_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_accept_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserAcceptRequestModel { #[serde(rename = "token")] pub token: String, diff --git a/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model.rs b/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model.rs index 0b80432ec..78bc34e17 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserBulkConfirmRequestModel { #[serde(rename = "keys")] pub keys: Vec, diff --git a/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model_entry.rs b/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model_entry.rs index eac03ea15..cc5c82d82 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model_entry.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_bulk_confirm_request_model_entry.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserBulkConfirmRequestModelEntry { #[serde(rename = "id")] pub id: uuid::Uuid, diff --git a/crates/bitwarden-api-api/src/models/provider_user_bulk_request_model.rs b/crates/bitwarden-api-api/src/models/provider_user_bulk_request_model.rs index 697206108..5e4cdca5a 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_bulk_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_bulk_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserBulkRequestModel { #[serde(rename = "ids")] pub ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model.rs b/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model.rs index de52b31f7..00da85ac4 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserBulkResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model_list_response_model.rs index 3954b0948..9f76667b9 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_bulk_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserBulkResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_user_confirm_request_model.rs b/crates/bitwarden-api-api/src/models/provider_user_confirm_request_model.rs index 333809dfd..917b858cf 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_confirm_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_confirm_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserConfirmRequestModel { #[serde(rename = "key")] pub key: String, diff --git a/crates/bitwarden-api-api/src/models/provider_user_invite_request_model.rs b/crates/bitwarden-api-api/src/models/provider_user_invite_request_model.rs index c547d5cb5..6113957bb 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_invite_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_invite_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserInviteRequestModel { #[serde(rename = "emails")] pub emails: Vec, diff --git a/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model.rs b/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model.rs index 9743af7b1..8dbd5f0a7 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserPublicKeyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model_list_response_model.rs index 04a8e5584..154070458 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_public_key_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserPublicKeyResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_user_response_model.rs b/crates/bitwarden-api-api/src/models/provider_user_response_model.rs index db7fac194..3ffeaa926 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_user_status_type.rs b/crates/bitwarden-api-api/src/models/provider_user_status_type.rs index 0918fe1d6..653dabbeb 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_status_type.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_status_type.rs @@ -14,23 +14,23 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum ProviderUserStatusType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, + Invited = 0, + Accepted = 1, + Confirmed = 2, } impl ToString for ProviderUserStatusType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::Invited => String::from("0"), + Self::Accepted => String::from("1"), + Self::Confirmed => String::from("2"), } } } impl Default for ProviderUserStatusType { fn default() -> ProviderUserStatusType { - Self::Variant0 + Self::Invited } } diff --git a/crates/bitwarden-api-api/src/models/provider_user_type.rs b/crates/bitwarden-api-api/src/models/provider_user_type.rs index 28b130f8d..c51a3b932 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_type.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum ProviderUserType { - Variant0 = 0, - Variant1 = 1, + ProviderAdmin = 0, + ServiceUser = 1, } impl ToString for ProviderUserType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::ProviderAdmin => String::from("0"), + Self::ServiceUser => String::from("1"), } } } impl Default for ProviderUserType { fn default() -> ProviderUserType { - Self::Variant0 + Self::ProviderAdmin } } diff --git a/crates/bitwarden-api-api/src/models/provider_user_update_request_model.rs b/crates/bitwarden-api-api/src/models/provider_user_update_request_model.rs index 8707fdb24..eeb36bff7 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserUpdateRequestModel { #[serde(rename = "type")] pub r#type: crate::models::ProviderUserType, diff --git a/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model.rs b/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model.rs index 774865640..74f6169cb 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserUserDetailsResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model_list_response_model.rs index 963b28c90..7e9a171fe 100644 --- a/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/provider_user_user_details_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ProviderUserUserDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/pub_key_cred_param.rs b/crates/bitwarden-api-api/src/models/pub_key_cred_param.rs new file mode 100644 index 000000000..670062fa1 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/pub_key_cred_param.rs @@ -0,0 +1,26 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PubKeyCredParam { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub r#type: Option, + #[serde(rename = "alg", skip_serializing_if = "Option::is_none")] + pub alg: Option, +} + +impl PubKeyCredParam { + pub fn new() -> PubKeyCredParam { + PubKeyCredParam { + r#type: None, + alg: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/public_key_credential_descriptor.rs b/crates/bitwarden-api-api/src/models/public_key_credential_descriptor.rs new file mode 100644 index 000000000..e266e1105 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/public_key_credential_descriptor.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PublicKeyCredentialDescriptor { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub r#type: Option, + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "transports", skip_serializing_if = "Option::is_none")] + pub transports: Option>, +} + +impl PublicKeyCredentialDescriptor { + pub fn new() -> PublicKeyCredentialDescriptor { + PublicKeyCredentialDescriptor { + r#type: None, + id: None, + transports: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/public_key_credential_rp_entity.rs b/crates/bitwarden-api-api/src/models/public_key_credential_rp_entity.rs new file mode 100644 index 000000000..318405f71 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/public_key_credential_rp_entity.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PublicKeyCredentialRpEntity { + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "icon", skip_serializing_if = "Option::is_none")] + pub icon: Option, +} + +impl PublicKeyCredentialRpEntity { + pub fn new() -> PublicKeyCredentialRpEntity { + PublicKeyCredentialRpEntity { + id: None, + name: None, + icon: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/push_registration_request_model.rs b/crates/bitwarden-api-api/src/models/push_registration_request_model.rs index 190ce4555..aca447e3f 100644 --- a/crates/bitwarden-api-api/src/models/push_registration_request_model.rs +++ b/crates/bitwarden-api-api/src/models/push_registration_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PushRegistrationRequestModel { #[serde(rename = "deviceId")] pub device_id: String, diff --git a/crates/bitwarden-api-api/src/models/push_send_request_model.rs b/crates/bitwarden-api-api/src/models/push_send_request_model.rs index 2ef909c16..d064b78e2 100644 --- a/crates/bitwarden-api-api/src/models/push_send_request_model.rs +++ b/crates/bitwarden-api-api/src/models/push_send_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PushSendRequestModel { #[serde(rename = "userId", skip_serializing_if = "Option::is_none")] pub user_id: Option, diff --git a/crates/bitwarden-api-api/src/models/push_type.rs b/crates/bitwarden-api-api/src/models/push_type.rs index fe38f85a7..8e69b5a73 100644 --- a/crates/bitwarden-api-api/src/models/push_type.rs +++ b/crates/bitwarden-api-api/src/models/push_type.rs @@ -14,51 +14,51 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum PushType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, - Variant8 = 8, - Variant9 = 9, - Variant10 = 10, - Variant11 = 11, - Variant12 = 12, - Variant13 = 13, - Variant14 = 14, - Variant15 = 15, - Variant16 = 16, + SyncCipherUpdate = 0, + SyncCipherCreate = 1, + SyncLoginDelete = 2, + SyncFolderDelete = 3, + SyncCiphers = 4, + SyncVault = 5, + SyncOrgKeys = 6, + SyncFolderCreate = 7, + SyncFolderUpdate = 8, + SyncCipherDelete = 9, + SyncSettings = 10, + LogOut = 11, + SyncSendCreate = 12, + SyncSendUpdate = 13, + SyncSendDelete = 14, + AuthRequest = 15, + AuthRequestResponse = 16, } impl ToString for PushType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), - Self::Variant8 => String::from("8"), - Self::Variant9 => String::from("9"), - Self::Variant10 => String::from("10"), - Self::Variant11 => String::from("11"), - Self::Variant12 => String::from("12"), - Self::Variant13 => String::from("13"), - Self::Variant14 => String::from("14"), - Self::Variant15 => String::from("15"), - Self::Variant16 => String::from("16"), + Self::SyncCipherUpdate => String::from("0"), + Self::SyncCipherCreate => String::from("1"), + Self::SyncLoginDelete => String::from("2"), + Self::SyncFolderDelete => String::from("3"), + Self::SyncCiphers => String::from("4"), + Self::SyncVault => String::from("5"), + Self::SyncOrgKeys => String::from("6"), + Self::SyncFolderCreate => String::from("7"), + Self::SyncFolderUpdate => String::from("8"), + Self::SyncCipherDelete => String::from("9"), + Self::SyncSettings => String::from("10"), + Self::LogOut => String::from("11"), + Self::SyncSendCreate => String::from("12"), + Self::SyncSendUpdate => String::from("13"), + Self::SyncSendDelete => String::from("14"), + Self::AuthRequest => String::from("15"), + Self::AuthRequestResponse => String::from("16"), } } } impl Default for PushType { fn default() -> PushType { - Self::Variant0 + Self::SyncCipherUpdate } } diff --git a/crates/bitwarden-api-api/src/models/push_update_request_model.rs b/crates/bitwarden-api-api/src/models/push_update_request_model.rs index a6030831a..2fda855bb 100644 --- a/crates/bitwarden-api-api/src/models/push_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/push_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PushUpdateRequestModel { #[serde(rename = "deviceIds")] pub device_ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/register_request_model.rs b/crates/bitwarden-api-api/src/models/register_request_model.rs index dbdd37ad6..b4a0d4b8d 100644 --- a/crates/bitwarden-api-api/src/models/register_request_model.rs +++ b/crates/bitwarden-api-api/src/models/register_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RegisterRequestModel { #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, diff --git a/crates/bitwarden-api-api/src/models/register_response_model.rs b/crates/bitwarden-api-api/src/models/register_response_model.rs index 55710d5b5..629d2af4f 100644 --- a/crates/bitwarden-api-api/src/models/register_response_model.rs +++ b/crates/bitwarden-api-api/src/models/register_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RegisterResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 new file mode 100644 index 000000000..c74c02592 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/reset_password_with_org_id_request_model.rs @@ -0,0 +1,26 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ResetPasswordWithOrgIdRequestModel { + #[serde(rename = "resetPasswordKey", skip_serializing_if = "Option::is_none")] + pub reset_password_key: Option, + #[serde(rename = "organizationId")] + pub organization_id: uuid::Uuid, +} + +impl ResetPasswordWithOrgIdRequestModel { + pub fn new(organization_id: uuid::Uuid) -> ResetPasswordWithOrgIdRequestModel { + ResetPasswordWithOrgIdRequestModel { + reset_password_key: None, + organization_id, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/response_data.rs b/crates/bitwarden-api-api/src/models/response_data.rs index 33f138cd6..9ff2a66e0 100644 --- a/crates/bitwarden-api-api/src/models/response_data.rs +++ b/crates/bitwarden-api-api/src/models/response_data.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ResponseData { #[serde(rename = "attestationObject", skip_serializing_if = "Option::is_none")] pub attestation_object: Option, diff --git a/crates/bitwarden-api-api/src/models/revoke_access_tokens_request.rs b/crates/bitwarden-api-api/src/models/revoke_access_tokens_request.rs index 53fcece6d..20d18ab59 100644 --- a/crates/bitwarden-api-api/src/models/revoke_access_tokens_request.rs +++ b/crates/bitwarden-api-api/src/models/revoke_access_tokens_request.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RevokeAccessTokensRequest { #[serde(rename = "ids")] pub ids: Vec, diff --git a/crates/bitwarden-api-api/src/models/saml2_binding_type.rs b/crates/bitwarden-api-api/src/models/saml2_binding_type.rs index 039309cad..e01b398cd 100644 --- a/crates/bitwarden-api-api/src/models/saml2_binding_type.rs +++ b/crates/bitwarden-api-api/src/models/saml2_binding_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum Saml2BindingType { - Variant1 = 1, - Variant2 = 2, + HttpRedirect = 1, + HttpPost = 2, } impl ToString for Saml2BindingType { fn to_string(&self) -> String { match self { - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::HttpRedirect => String::from("1"), + Self::HttpPost => String::from("2"), } } } impl Default for Saml2BindingType { fn default() -> Saml2BindingType { - Self::Variant1 + Self::HttpRedirect } } diff --git a/crates/bitwarden-api-api/src/models/saml2_name_id_format.rs b/crates/bitwarden-api-api/src/models/saml2_name_id_format.rs index 6399c7f9b..e8680e9a6 100644 --- a/crates/bitwarden-api-api/src/models/saml2_name_id_format.rs +++ b/crates/bitwarden-api-api/src/models/saml2_name_id_format.rs @@ -14,35 +14,35 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum Saml2NameIdFormat { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, - Variant8 = 8, + NotConfigured = 0, + Unspecified = 1, + EmailAddress = 2, + X509SubjectName = 3, + WindowsDomainQualifiedName = 4, + KerberosPrincipalName = 5, + EntityIdentifier = 6, + Persistent = 7, + Transient = 8, } impl ToString for Saml2NameIdFormat { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), - Self::Variant8 => String::from("8"), + Self::NotConfigured => String::from("0"), + Self::Unspecified => String::from("1"), + Self::EmailAddress => String::from("2"), + Self::X509SubjectName => String::from("3"), + Self::WindowsDomainQualifiedName => String::from("4"), + Self::KerberosPrincipalName => String::from("5"), + Self::EntityIdentifier => String::from("6"), + Self::Persistent => String::from("7"), + Self::Transient => String::from("8"), } } } impl Default for Saml2NameIdFormat { fn default() -> Saml2NameIdFormat { - Self::Variant0 + Self::NotConfigured } } diff --git a/crates/bitwarden-api-api/src/models/saml2_signing_behavior.rs b/crates/bitwarden-api-api/src/models/saml2_signing_behavior.rs index 3b0668865..12be578aa 100644 --- a/crates/bitwarden-api-api/src/models/saml2_signing_behavior.rs +++ b/crates/bitwarden-api-api/src/models/saml2_signing_behavior.rs @@ -14,23 +14,23 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum Saml2SigningBehavior { - Variant0 = 0, - Variant1 = 1, - Variant3 = 3, + IfIdpWantAuthnRequestsSigned = 0, + Always = 1, + Never = 3, } impl ToString for Saml2SigningBehavior { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant3 => String::from("3"), + Self::IfIdpWantAuthnRequestsSigned => String::from("0"), + Self::Always => String::from("1"), + Self::Never => String::from("3"), } } } impl Default for Saml2SigningBehavior { fn default() -> Saml2SigningBehavior { - Self::Variant0 + Self::IfIdpWantAuthnRequestsSigned } } 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 a99bc551e..7bc23b871 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretCreateRequestModel { #[serde(rename = "key")] pub key: String, diff --git a/crates/bitwarden-api-api/src/models/secret_response_inner_project.rs b/crates/bitwarden-api-api/src/models/secret_response_inner_project.rs index bdd2c4fe0..25be360ad 100644 --- a/crates/bitwarden-api-api/src/models/secret_response_inner_project.rs +++ b/crates/bitwarden-api-api/src/models/secret_response_inner_project.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretResponseInnerProject { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/secret_response_model.rs b/crates/bitwarden-api-api/src/models/secret_response_model.rs index 2dcf8b192..dffea3122 100644 --- a/crates/bitwarden-api-api/src/models/secret_response_model.rs +++ b/crates/bitwarden-api-api/src/models/secret_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 a4675c6ba..67ae4ade2 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretUpdateRequestModel { #[serde(rename = "key")] pub key: String, diff --git a/crates/bitwarden-api-api/src/models/secret_verification_request_model.rs b/crates/bitwarden-api-api/src/models/secret_verification_request_model.rs index f4774d5b6..85ceee419 100644 --- a/crates/bitwarden-api-api/src/models/secret_verification_request_model.rs +++ b/crates/bitwarden-api-api/src/models/secret_verification_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretVerificationRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/secret_with_projects_inner_project.rs b/crates/bitwarden-api-api/src/models/secret_with_projects_inner_project.rs index 3b68f65d5..cce3b239c 100644 --- a/crates/bitwarden-api-api/src/models/secret_with_projects_inner_project.rs +++ b/crates/bitwarden-api-api/src/models/secret_with_projects_inner_project.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretWithProjectsInnerProject { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/secret_with_projects_list_response_model.rs b/crates/bitwarden-api-api/src/models/secret_with_projects_list_response_model.rs index 1a832ab48..046e698f4 100644 --- a/crates/bitwarden-api-api/src/models/secret_with_projects_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/secret_with_projects_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretWithProjectsListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/secrets_manager_plan_features_response_model.rs b/crates/bitwarden-api-api/src/models/secrets_manager_plan_features_response_model.rs new file mode 100644 index 000000000..ef8d32f04 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/secrets_manager_plan_features_response_model.rs @@ -0,0 +1,86 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SecretsManagerPlanFeaturesResponseModel { + #[serde(rename = "maxServiceAccounts", skip_serializing_if = "Option::is_none")] + pub max_service_accounts: Option, + #[serde( + rename = "allowServiceAccountsAutoscale", + skip_serializing_if = "Option::is_none" + )] + pub allow_service_accounts_autoscale: Option, + #[serde( + rename = "stripeServiceAccountPlanId", + skip_serializing_if = "Option::is_none" + )] + pub stripe_service_account_plan_id: Option, + #[serde( + rename = "additionalPricePerServiceAccount", + skip_serializing_if = "Option::is_none" + )] + pub additional_price_per_service_account: Option, + #[serde(rename = "baseServiceAccount", skip_serializing_if = "Option::is_none")] + pub base_service_account: Option, + #[serde( + rename = "maxAdditionalServiceAccount", + skip_serializing_if = "Option::is_none" + )] + pub max_additional_service_account: Option, + #[serde( + rename = "hasAdditionalServiceAccountOption", + skip_serializing_if = "Option::is_none" + )] + pub has_additional_service_account_option: Option, + #[serde(rename = "stripeSeatPlanId", skip_serializing_if = "Option::is_none")] + pub stripe_seat_plan_id: Option, + #[serde( + rename = "hasAdditionalSeatsOption", + skip_serializing_if = "Option::is_none" + )] + pub has_additional_seats_option: 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 = "baseSeats", skip_serializing_if = "Option::is_none")] + pub base_seats: Option, + #[serde(rename = "maxSeats", skip_serializing_if = "Option::is_none")] + pub max_seats: Option, + #[serde(rename = "maxAdditionalSeats", skip_serializing_if = "Option::is_none")] + pub max_additional_seats: Option, + #[serde(rename = "allowSeatAutoscale", skip_serializing_if = "Option::is_none")] + pub allow_seat_autoscale: Option, + #[serde(rename = "maxProjects", skip_serializing_if = "Option::is_none")] + pub max_projects: Option, +} + +impl SecretsManagerPlanFeaturesResponseModel { + pub fn new() -> SecretsManagerPlanFeaturesResponseModel { + SecretsManagerPlanFeaturesResponseModel { + max_service_accounts: None, + allow_service_accounts_autoscale: None, + stripe_service_account_plan_id: None, + additional_price_per_service_account: None, + base_service_account: None, + max_additional_service_account: None, + has_additional_service_account_option: None, + stripe_seat_plan_id: None, + has_additional_seats_option: None, + base_price: None, + seat_price: None, + base_seats: None, + max_seats: None, + max_additional_seats: None, + allow_seat_autoscale: None, + max_projects: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/secrets_manager_subscribe_request_model.rs b/crates/bitwarden-api-api/src/models/secrets_manager_subscribe_request_model.rs new file mode 100644 index 000000000..cf27fa764 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/secrets_manager_subscribe_request_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SecretsManagerSubscribeRequestModel { + #[serde(rename = "additionalSmSeats")] + pub additional_sm_seats: i32, + #[serde(rename = "additionalServiceAccounts")] + pub additional_service_accounts: i32, +} + +impl SecretsManagerSubscribeRequestModel { + pub fn new( + additional_sm_seats: i32, + additional_service_accounts: i32, + ) -> SecretsManagerSubscribeRequestModel { + SecretsManagerSubscribeRequestModel { + additional_sm_seats, + additional_service_accounts, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/secrets_manager_subscription_update_request_model.rs b/crates/bitwarden-api-api/src/models/secrets_manager_subscription_update_request_model.rs index 406769bce..b61e9c2fe 100644 --- a/crates/bitwarden-api-api/src/models/secrets_manager_subscription_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/secrets_manager_subscription_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretsManagerSubscriptionUpdateRequestModel { #[serde(rename = "seatAdjustment")] pub seat_adjustment: i32, diff --git a/crates/bitwarden-api-api/src/models/secrets_with_projects_inner_secret.rs b/crates/bitwarden-api-api/src/models/secrets_with_projects_inner_secret.rs index 45dcbd9c1..2b6673ef1 100644 --- a/crates/bitwarden-api-api/src/models/secrets_with_projects_inner_secret.rs +++ b/crates/bitwarden-api-api/src/models/secrets_with_projects_inner_secret.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SecretsWithProjectsInnerSecret { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/secure_note_type.rs b/crates/bitwarden-api-api/src/models/secure_note_type.rs index 9fd0a3158..dd8516f6e 100644 --- a/crates/bitwarden-api-api/src/models/secure_note_type.rs +++ b/crates/bitwarden-api-api/src/models/secure_note_type.rs @@ -14,19 +14,19 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum SecureNoteType { - Variant0 = 0, + Generic = 0, } impl ToString for SecureNoteType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), + Self::Generic => String::from("0"), } } } impl Default for SecureNoteType { fn default() -> SecureNoteType { - Self::Variant0 + Self::Generic } } diff --git a/crates/bitwarden-api-api/src/models/selection_read_only_request_model.rs b/crates/bitwarden-api-api/src/models/selection_read_only_request_model.rs index 2d7d0c4da..81695f1d7 100644 --- a/crates/bitwarden-api-api/src/models/selection_read_only_request_model.rs +++ b/crates/bitwarden-api-api/src/models/selection_read_only_request_model.rs @@ -8,22 +8,25 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SelectionReadOnlyRequestModel { #[serde(rename = "id")] - pub id: String, + pub id: uuid::Uuid, #[serde(rename = "readOnly", skip_serializing_if = "Option::is_none")] pub read_only: Option, #[serde(rename = "hidePasswords", skip_serializing_if = "Option::is_none")] pub hide_passwords: Option, + #[serde(rename = "manage", skip_serializing_if = "Option::is_none")] + pub manage: Option, } impl SelectionReadOnlyRequestModel { - pub fn new(id: String) -> SelectionReadOnlyRequestModel { + pub fn new(id: uuid::Uuid) -> SelectionReadOnlyRequestModel { SelectionReadOnlyRequestModel { id, read_only: None, hide_passwords: None, + manage: None, } } } diff --git a/crates/bitwarden-api-api/src/models/selection_read_only_response_model.rs b/crates/bitwarden-api-api/src/models/selection_read_only_response_model.rs index c989fccf0..b24fd8368 100644 --- a/crates/bitwarden-api-api/src/models/selection_read_only_response_model.rs +++ b/crates/bitwarden-api-api/src/models/selection_read_only_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SelectionReadOnlyResponseModel { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, @@ -16,6 +16,8 @@ pub struct SelectionReadOnlyResponseModel { pub read_only: Option, #[serde(rename = "hidePasswords", skip_serializing_if = "Option::is_none")] pub hide_passwords: Option, + #[serde(rename = "manage", skip_serializing_if = "Option::is_none")] + pub manage: Option, } impl SelectionReadOnlyResponseModel { @@ -24,6 +26,7 @@ impl SelectionReadOnlyResponseModel { id: None, read_only: None, hide_passwords: None, + manage: None, } } } diff --git a/crates/bitwarden-api-api/src/models/self_hosted_organization_license_request_model.rs b/crates/bitwarden-api-api/src/models/self_hosted_organization_license_request_model.rs index 935589714..3e5038e38 100644 --- a/crates/bitwarden-api-api/src/models/self_hosted_organization_license_request_model.rs +++ b/crates/bitwarden-api-api/src/models/self_hosted_organization_license_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SelfHostedOrganizationLicenseRequestModel { #[serde(rename = "licenseKey", skip_serializing_if = "Option::is_none")] pub license_key: Option, diff --git a/crates/bitwarden-api-api/src/models/send_access_request_model.rs b/crates/bitwarden-api-api/src/models/send_access_request_model.rs index 2cdff93c3..51192dc02 100644 --- a/crates/bitwarden-api-api/src/models/send_access_request_model.rs +++ b/crates/bitwarden-api-api/src/models/send_access_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendAccessRequestModel { #[serde(rename = "password", skip_serializing_if = "Option::is_none")] pub password: Option, diff --git a/crates/bitwarden-api-api/src/models/send_file_model.rs b/crates/bitwarden-api-api/src/models/send_file_model.rs index 88d15c50e..e94c99ea2 100644 --- a/crates/bitwarden-api-api/src/models/send_file_model.rs +++ b/crates/bitwarden-api-api/src/models/send_file_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendFileModel { #[serde(rename = "id", skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/crates/bitwarden-api-api/src/models/send_file_upload_data_response_model.rs b/crates/bitwarden-api-api/src/models/send_file_upload_data_response_model.rs index 0c22f4126..0f1329824 100644 --- a/crates/bitwarden-api-api/src/models/send_file_upload_data_response_model.rs +++ b/crates/bitwarden-api-api/src/models/send_file_upload_data_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendFileUploadDataResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/send_request_model.rs b/crates/bitwarden-api-api/src/models/send_request_model.rs index 2d528623c..e117b82fb 100644 --- a/crates/bitwarden-api-api/src/models/send_request_model.rs +++ b/crates/bitwarden-api-api/src/models/send_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendRequestModel { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, diff --git a/crates/bitwarden-api-api/src/models/send_response_model.rs b/crates/bitwarden-api-api/src/models/send_response_model.rs index 76af62ed4..c74ddd7c4 100644 --- a/crates/bitwarden-api-api/src/models/send_response_model.rs +++ b/crates/bitwarden-api-api/src/models/send_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/send_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/send_response_model_list_response_model.rs index 608927413..c0870e19c 100644 --- a/crates/bitwarden-api-api/src/models/send_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/send_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/send_text_model.rs b/crates/bitwarden-api-api/src/models/send_text_model.rs index 3c37ba152..58f2effd0 100644 --- a/crates/bitwarden-api-api/src/models/send_text_model.rs +++ b/crates/bitwarden-api-api/src/models/send_text_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendTextModel { #[serde(rename = "text", skip_serializing_if = "Option::is_none")] pub text: Option, diff --git a/crates/bitwarden-api-api/src/models/send_type.rs b/crates/bitwarden-api-api/src/models/send_type.rs index 5ea1c2504..6364c4af2 100644 --- a/crates/bitwarden-api-api/src/models/send_type.rs +++ b/crates/bitwarden-api-api/src/models/send_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum SendType { - Variant0 = 0, - Variant1 = 1, + Text = 0, + File = 1, } impl ToString for SendType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::Text => String::from("0"), + Self::File => String::from("1"), } } } impl Default for SendType { fn default() -> SendType { - Self::Variant0 + Self::Text } } diff --git a/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs b/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs index 8c31ae2ff..06dc03a95 100644 --- a/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs +++ b/crates/bitwarden-api-api/src/models/send_with_id_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SendWithIdRequestModel { #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub r#type: Option, diff --git a/crates/bitwarden-api-api/src/models/server_config_response_model.rs b/crates/bitwarden-api-api/src/models/server_config_response_model.rs index 46d417394..a5603808f 100644 --- a/crates/bitwarden-api-api/src/models/server_config_response_model.rs +++ b/crates/bitwarden-api-api/src/models/server_config_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ServerConfigResponseModel { #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, diff --git a/crates/bitwarden-api-api/src/models/service_account_create_request_model.rs b/crates/bitwarden-api-api/src/models/service_account_create_request_model.rs index f34686774..ebebc8f96 100644 --- a/crates/bitwarden-api-api/src/models/service_account_create_request_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_create_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ServiceAccountCreateRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/service_account_access_policies_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_people_access_policies_response_model.rs similarity index 74% rename from crates/bitwarden-api-api/src/models/service_account_access_policies_response_model.rs rename to crates/bitwarden-api-api/src/models/service_account_people_access_policies_response_model.rs index 22db48178..3b38851f4 100644 --- a/crates/bitwarden-api-api/src/models/service_account_access_policies_response_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_people_access_policies_response_model.rs @@ -8,8 +8,8 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct ServiceAccountAccessPoliciesResponseModel { +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ServiceAccountPeopleAccessPoliciesResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, #[serde(rename = "userAccessPolicies", skip_serializing_if = "Option::is_none")] @@ -23,9 +23,9 @@ pub struct ServiceAccountAccessPoliciesResponseModel { Option>, } -impl ServiceAccountAccessPoliciesResponseModel { - pub fn new() -> ServiceAccountAccessPoliciesResponseModel { - ServiceAccountAccessPoliciesResponseModel { +impl ServiceAccountPeopleAccessPoliciesResponseModel { + pub fn new() -> ServiceAccountPeopleAccessPoliciesResponseModel { + ServiceAccountPeopleAccessPoliciesResponseModel { object: None, user_access_policies: None, group_access_policies: 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/service_account_project_access_policy_response_model.rs index f734f6538..8eb850e01 100644 --- a/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ServiceAccountProjectAccessPolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model_list_response_model.rs index 307f2b333..531970536 100644 --- a/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_project_access_policy_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ServiceAccountProjectAccessPolicyResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/service_account_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_response_model.rs index ec43e6014..0cba8669a 100644 --- a/crates/bitwarden-api-api/src/models/service_account_response_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ServiceAccountResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/inner_secret.rs b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model.rs similarity index 50% rename from crates/bitwarden-api-api/src/models/inner_secret.rs rename to crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model.rs index 43deb0f1a..f8b8ce339 100644 --- a/crates/bitwarden-api-api/src/models/inner_secret.rs +++ b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model.rs @@ -8,37 +8,34 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct InnerSecret { +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ServiceAccountSecretsDetailsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, #[serde(rename = "id", skip_serializing_if = "Option::is_none")] - pub id: Option, + pub id: Option, #[serde(rename = "organizationId", skip_serializing_if = "Option::is_none")] - pub organization_id: Option, - #[serde(rename = "key", skip_serializing_if = "Option::is_none")] - pub key: Option, + pub organization_id: Option, + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, #[serde(rename = "creationDate", skip_serializing_if = "Option::is_none")] pub creation_date: Option, #[serde(rename = "revisionDate", skip_serializing_if = "Option::is_none")] pub revision_date: Option, - #[serde(rename = "projects", skip_serializing_if = "Option::is_none")] - pub projects: 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 = "accessToSecrets", skip_serializing_if = "Option::is_none")] + pub access_to_secrets: Option, } -impl InnerSecret { - pub fn new() -> InnerSecret { - InnerSecret { +impl ServiceAccountSecretsDetailsResponseModel { + pub fn new() -> ServiceAccountSecretsDetailsResponseModel { + ServiceAccountSecretsDetailsResponseModel { + object: None, id: None, organization_id: None, - key: None, + name: None, creation_date: None, revision_date: None, - projects: None, - read: None, - write: None, + access_to_secrets: None, } } } diff --git a/crates/bitwarden-api-api/src/models/service_account_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model_list_response_model.rs similarity index 60% rename from crates/bitwarden-api-api/src/models/service_account_response_model_list_response_model.rs rename to crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model_list_response_model.rs index 41ad4c28a..81ac10efa 100644 --- a/crates/bitwarden-api-api/src/models/service_account_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_secrets_details_response_model_list_response_model.rs @@ -8,19 +8,19 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct ServiceAccountResponseModelListResponseModel { +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ServiceAccountSecretsDetailsResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, #[serde(rename = "data", skip_serializing_if = "Option::is_none")] - pub data: Option>, + pub data: Option>, #[serde(rename = "continuationToken", skip_serializing_if = "Option::is_none")] pub continuation_token: Option, } -impl ServiceAccountResponseModelListResponseModel { - pub fn new() -> ServiceAccountResponseModelListResponseModel { - ServiceAccountResponseModelListResponseModel { +impl ServiceAccountSecretsDetailsResponseModelListResponseModel { + pub fn new() -> ServiceAccountSecretsDetailsResponseModelListResponseModel { + ServiceAccountSecretsDetailsResponseModelListResponseModel { object: None, data: None, continuation_token: None, diff --git a/crates/bitwarden-api-api/src/models/service_account_update_request_model.rs b/crates/bitwarden-api-api/src/models/service_account_update_request_model.rs index 842760819..400617ed8 100644 --- a/crates/bitwarden-api-api/src/models/service_account_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/service_account_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ServiceAccountUpdateRequestModel { #[serde(rename = "name")] pub name: String, diff --git a/crates/bitwarden-api-api/src/models/set_key_connector_key_request_model.rs b/crates/bitwarden-api-api/src/models/set_key_connector_key_request_model.rs index a39326e05..8da90aa4e 100644 --- a/crates/bitwarden-api-api/src/models/set_key_connector_key_request_model.rs +++ b/crates/bitwarden-api-api/src/models/set_key_connector_key_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SetKeyConnectorKeyRequestModel { #[serde(rename = "key")] pub key: String, diff --git a/crates/bitwarden-api-api/src/models/set_password_request_model.rs b/crates/bitwarden-api-api/src/models/set_password_request_model.rs index f7b3e3e61..30b04c9fc 100644 --- a/crates/bitwarden-api-api/src/models/set_password_request_model.rs +++ b/crates/bitwarden-api-api/src/models/set_password_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SetPasswordRequestModel { #[serde(rename = "masterPasswordHash")] pub master_password_hash: String, @@ -16,8 +16,8 @@ pub struct SetPasswordRequestModel { pub key: String, #[serde(rename = "masterPasswordHint", skip_serializing_if = "Option::is_none")] pub master_password_hint: Option, - #[serde(rename = "keys")] - pub keys: Box, + #[serde(rename = "keys", skip_serializing_if = "Option::is_none")] + pub keys: Option>, #[serde(rename = "kdf")] pub kdf: crate::models::KdfType, #[serde(rename = "kdfIterations")] @@ -34,7 +34,6 @@ impl SetPasswordRequestModel { pub fn new( master_password_hash: String, key: String, - keys: crate::models::KeysRequestModel, kdf: crate::models::KdfType, kdf_iterations: i32, ) -> SetPasswordRequestModel { @@ -42,7 +41,7 @@ impl SetPasswordRequestModel { master_password_hash, key, master_password_hint: None, - keys: Box::new(keys), + keys: None, kdf, kdf_iterations, kdf_memory: None, diff --git a/crates/bitwarden-api-api/src/models/sm_export_response_model.rs b/crates/bitwarden-api-api/src/models/sm_export_response_model.rs index 60ab8697c..71f821254 100644 --- a/crates/bitwarden-api-api/src/models/sm_export_response_model.rs +++ b/crates/bitwarden-api-api/src/models/sm_export_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SmExportResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/sm_import_request_model.rs b/crates/bitwarden-api-api/src/models/sm_import_request_model.rs index 431a87fe4..68e53efbb 100644 --- a/crates/bitwarden-api-api/src/models/sm_import_request_model.rs +++ b/crates/bitwarden-api-api/src/models/sm_import_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SmImportRequestModel { #[serde(rename = "projects", skip_serializing_if = "Option::is_none")] pub projects: Option>, diff --git a/crates/bitwarden-api-api/src/models/sso_configuration_data.rs b/crates/bitwarden-api-api/src/models/sso_configuration_data.rs index 0e2890ad9..1cf5ea714 100644 --- a/crates/bitwarden-api-api/src/models/sso_configuration_data.rs +++ b/crates/bitwarden-api-api/src/models/sso_configuration_data.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SsoConfigurationData { #[serde(rename = "configType", skip_serializing_if = "Option::is_none")] pub config_type: Option, @@ -17,7 +17,10 @@ pub struct SsoConfigurationData { skip_serializing_if = "Option::is_none" )] pub member_decryption_type: Option, - /// Legacy property to determine if KeyConnector was enabled. Kept for backwards compatibility with old configs that will not have the new Bit.Core.Auth.Models.Data.SsoConfigurationData.MemberDecryptionType when deserialized from the database. + /// Legacy property to determine if KeyConnector was enabled. Kept for backwards compatibility + /// with old configs that will not have the new + /// Bit.Core.Auth.Models.Data.SsoConfigurationData.MemberDecryptionType when deserialized from + /// the database. #[serde( rename = "keyConnectorEnabled", skip_serializing_if = "Option::is_none" @@ -105,6 +108,8 @@ pub struct SsoConfigurationData { skip_serializing_if = "Option::is_none" )] pub idp_want_authn_requests_signed: Option, + #[serde(rename = "spUniqueEntityId", skip_serializing_if = "Option::is_none")] + pub sp_unique_entity_id: Option, #[serde(rename = "spNameIdFormat", skip_serializing_if = "Option::is_none")] pub sp_name_id_format: Option, #[serde( @@ -160,6 +165,7 @@ impl SsoConfigurationData { idp_disable_outbound_logout_requests: None, idp_outbound_signing_algorithm: None, idp_want_authn_requests_signed: None, + sp_unique_entity_id: None, sp_name_id_format: None, sp_outbound_signing_algorithm: None, sp_signing_behavior: None, diff --git a/crates/bitwarden-api-api/src/models/sso_configuration_data_request.rs b/crates/bitwarden-api-api/src/models/sso_configuration_data_request.rs index 8e6f18ced..7b26413c8 100644 --- a/crates/bitwarden-api-api/src/models/sso_configuration_data_request.rs +++ b/crates/bitwarden-api-api/src/models/sso_configuration_data_request.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SsoConfigurationDataRequest { #[serde(rename = "configType")] pub config_type: crate::models::SsoType, @@ -63,6 +63,8 @@ pub struct SsoConfigurationDataRequest { skip_serializing_if = "Option::is_none" )] pub expected_return_acr_value: Option, + #[serde(rename = "spUniqueEntityId", skip_serializing_if = "Option::is_none")] + pub sp_unique_entity_id: Option, #[serde(rename = "spNameIdFormat", skip_serializing_if = "Option::is_none")] pub sp_name_id_format: Option, #[serde( @@ -149,6 +151,7 @@ impl SsoConfigurationDataRequest { additional_name_claim_types: None, acr_values: None, expected_return_acr_value: None, + sp_unique_entity_id: None, sp_name_id_format: None, sp_outbound_signing_algorithm: None, sp_signing_behavior: None, diff --git a/crates/bitwarden-api-api/src/models/sso_type.rs b/crates/bitwarden-api-api/src/models/sso_type.rs index 3e1aefa4d..9ff337676 100644 --- a/crates/bitwarden-api-api/src/models/sso_type.rs +++ b/crates/bitwarden-api-api/src/models/sso_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum SsoType { - Variant1 = 1, - Variant2 = 2, + OpenIdConnect = 1, + Saml2 = 2, } impl ToString for SsoType { fn to_string(&self) -> String { match self { - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), + Self::OpenIdConnect => String::from("1"), + Self::Saml2 => String::from("2"), } } } impl Default for SsoType { fn default() -> SsoType { - Self::Variant1 + Self::OpenIdConnect } } diff --git a/crates/bitwarden-api-api/src/models/sso_urls.rs b/crates/bitwarden-api-api/src/models/sso_urls.rs index 45bb286f0..fa0b03cfb 100644 --- a/crates/bitwarden-api-api/src/models/sso_urls.rs +++ b/crates/bitwarden-api-api/src/models/sso_urls.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SsoUrls { #[serde(rename = "callbackPath", skip_serializing_if = "Option::is_none")] pub callback_path: Option, @@ -19,6 +19,8 @@ pub struct SsoUrls { pub signed_out_callback_path: Option, #[serde(rename = "spEntityId", skip_serializing_if = "Option::is_none")] pub sp_entity_id: Option, + #[serde(rename = "spEntityIdStatic", skip_serializing_if = "Option::is_none")] + pub sp_entity_id_static: Option, #[serde(rename = "spMetadataUrl", skip_serializing_if = "Option::is_none")] pub sp_metadata_url: Option, #[serde(rename = "spAcsUrl", skip_serializing_if = "Option::is_none")] @@ -31,6 +33,7 @@ impl SsoUrls { callback_path: None, signed_out_callback_path: None, sp_entity_id: None, + sp_entity_id_static: None, sp_metadata_url: None, sp_acs_url: None, } diff --git a/crates/bitwarden-api-api/src/models/storage_request_model.rs b/crates/bitwarden-api-api/src/models/storage_request_model.rs index 5625f8874..8d7778a73 100644 --- a/crates/bitwarden-api-api/src/models/storage_request_model.rs +++ b/crates/bitwarden-api-api/src/models/storage_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct StorageRequestModel { #[serde(rename = "storageGbAdjustment")] pub storage_gb_adjustment: i32, diff --git a/crates/bitwarden-api-api/src/models/subscription_response_model.rs b/crates/bitwarden-api-api/src/models/subscription_response_model.rs index 2ebba2a44..a3d80dba3 100644 --- a/crates/bitwarden-api-api/src/models/subscription_response_model.rs +++ b/crates/bitwarden-api-api/src/models/subscription_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SubscriptionResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -26,8 +26,6 @@ pub struct SubscriptionResponseModel { pub license: Option>, #[serde(rename = "expiration", skip_serializing_if = "Option::is_none")] pub expiration: Option, - #[serde(rename = "usingInAppPurchase", skip_serializing_if = "Option::is_none")] - pub using_in_app_purchase: Option, } impl SubscriptionResponseModel { @@ -41,7 +39,6 @@ impl SubscriptionResponseModel { subscription: None, license: None, expiration: None, - using_in_app_purchase: None, } } } diff --git a/crates/bitwarden-api-api/src/models/sync_response_model.rs b/crates/bitwarden-api-api/src/models/sync_response_model.rs index 60b1d5f7b..757cecc14 100644 --- a/crates/bitwarden-api-api/src/models/sync_response_model.rs +++ b/crates/bitwarden-api-api/src/models/sync_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SyncResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/tax_info_response_model.rs b/crates/bitwarden-api-api/src/models/tax_info_response_model.rs index 344e80f45..e7825b67c 100644 --- a/crates/bitwarden-api-api/src/models/tax_info_response_model.rs +++ b/crates/bitwarden-api-api/src/models/tax_info_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TaxInfoResponseModel { #[serde(rename = "taxIdNumber", skip_serializing_if = "Option::is_none")] pub tax_id_number: Option, diff --git a/crates/bitwarden-api-api/src/models/tax_info_update_request_model.rs b/crates/bitwarden-api-api/src/models/tax_info_update_request_model.rs index 969528e53..153fb27b0 100644 --- a/crates/bitwarden-api-api/src/models/tax_info_update_request_model.rs +++ b/crates/bitwarden-api-api/src/models/tax_info_update_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TaxInfoUpdateRequestModel { #[serde(rename = "country")] pub country: String, diff --git a/crates/bitwarden-api-api/src/models/tax_rate_response_model.rs b/crates/bitwarden-api-api/src/models/tax_rate_response_model.rs index 3ae819bf6..3980008c3 100644 --- a/crates/bitwarden-api-api/src/models/tax_rate_response_model.rs +++ b/crates/bitwarden-api-api/src/models/tax_rate_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TaxRateResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/tax_rate_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/tax_rate_response_model_list_response_model.rs index 5c23891b1..496b713ff 100644 --- a/crates/bitwarden-api-api/src/models/tax_rate_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/tax_rate_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TaxRateResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/transaction_type.rs b/crates/bitwarden-api-api/src/models/transaction_type.rs index c9cda7c76..1bb69ccbb 100644 --- a/crates/bitwarden-api-api/src/models/transaction_type.rs +++ b/crates/bitwarden-api-api/src/models/transaction_type.rs @@ -14,27 +14,27 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum TransactionType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, + Charge = 0, + Credit = 1, + PromotionalCredit = 2, + ReferralCredit = 3, + Refund = 4, } impl ToString for TransactionType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), + Self::Charge => String::from("0"), + Self::Credit => String::from("1"), + Self::PromotionalCredit => String::from("2"), + Self::ReferralCredit => String::from("3"), + Self::Refund => String::from("4"), } } } impl Default for TransactionType { fn default() -> TransactionType { - Self::Variant0 + Self::Charge } } 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 dde60927c..cf2707e15 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorAuthenticatorResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, 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 833a98a3d..e0afc8212 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorDuoResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_email_request_model.rs b/crates/bitwarden-api-api/src/models/two_factor_email_request_model.rs index 614ad8f80..7d121412a 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_email_request_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_email_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorEmailRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_email_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_email_response_model.rs index afbbafd8d..a976dba0f 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_email_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_email_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorEmailResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_provider_request_model.rs b/crates/bitwarden-api-api/src/models/two_factor_provider_request_model.rs index 7c9b231b0..7b22d2a6b 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_provider_request_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_provider_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorProviderRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_provider_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_provider_response_model.rs index dbca32167..0c97a3088 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_provider_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_provider_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorProviderResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_provider_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_provider_response_model_list_response_model.rs index 7b71fee31..73a7b4d57 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_provider_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_provider_response_model_list_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorProviderResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_provider_type.rs b/crates/bitwarden-api-api/src/models/two_factor_provider_type.rs index 00188f0fd..94d20ee2a 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_provider_type.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_provider_type.rs @@ -14,33 +14,33 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum TwoFactorProviderType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, - Variant6 = 6, - Variant7 = 7, + Authenticator = 0, + Email = 1, + Duo = 2, + YubiKey = 3, + U2f = 4, + Remember = 5, + OrganizationDuo = 6, + WebAuthn = 7, } impl ToString for TwoFactorProviderType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), - Self::Variant6 => String::from("6"), - Self::Variant7 => String::from("7"), + Self::Authenticator => String::from("0"), + Self::Email => String::from("1"), + Self::Duo => String::from("2"), + Self::YubiKey => String::from("3"), + Self::U2f => String::from("4"), + Self::Remember => String::from("5"), + Self::OrganizationDuo => String::from("6"), + Self::WebAuthn => String::from("7"), } } } impl Default for TwoFactorProviderType { fn default() -> TwoFactorProviderType { - Self::Variant0 + Self::Authenticator } } diff --git a/crates/bitwarden-api-api/src/models/two_factor_recover_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_recover_response_model.rs index 2267c134a..c35ce8a17 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_recover_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_recover_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorRecoverResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_recovery_request_model.rs b/crates/bitwarden-api-api/src/models/two_factor_recovery_request_model.rs index 2ceb17a83..425d528b3 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_recovery_request_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_recovery_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorRecoveryRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_web_authn_delete_request_model.rs b/crates/bitwarden-api-api/src/models/two_factor_web_authn_delete_request_model.rs index f104792fd..e981d0e5d 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_web_authn_delete_request_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_web_authn_delete_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorWebAuthnDeleteRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_web_authn_request_model.rs b/crates/bitwarden-api-api/src/models/two_factor_web_authn_request_model.rs index 34c7e1a9b..2139ad022 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_web_authn_request_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_web_authn_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorWebAuthnRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_web_authn_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_web_authn_response_model.rs index 96cb72e96..c372f2f45 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_web_authn_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_web_authn_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorWebAuthnResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/two_factor_yubi_key_response_model.rs b/crates/bitwarden-api-api/src/models/two_factor_yubi_key_response_model.rs index 1fecc82f3..f2122d29a 100644 --- a/crates/bitwarden-api-api/src/models/two_factor_yubi_key_response_model.rs +++ b/crates/bitwarden-api-api/src/models/two_factor_yubi_key_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TwoFactorYubiKeyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/update_avatar_request_model.rs b/crates/bitwarden-api-api/src/models/update_avatar_request_model.rs index ea00c08c5..ea648983e 100644 --- a/crates/bitwarden-api-api/src/models/update_avatar_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_avatar_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateAvatarRequestModel { #[serde(rename = "avatarColor", skip_serializing_if = "Option::is_none")] pub avatar_color: Option, diff --git a/crates/bitwarden-api-api/src/models/update_devices_trust_request_model.rs b/crates/bitwarden-api-api/src/models/update_devices_trust_request_model.rs new file mode 100644 index 000000000..ab69cf249 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/update_devices_trust_request_model.rs @@ -0,0 +1,43 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct UpdateDevicesTrustRequestModel { + #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] + pub master_password_hash: Option, + #[serde(rename = "otp", skip_serializing_if = "Option::is_none")] + pub otp: Option, + #[serde( + rename = "authRequestAccessCode", + skip_serializing_if = "Option::is_none" + )] + pub auth_request_access_code: Option, + #[serde(rename = "secret", skip_serializing_if = "Option::is_none")] + pub secret: Option, + #[serde(rename = "currentDevice")] + pub current_device: Box, + #[serde(rename = "otherDevices", skip_serializing_if = "Option::is_none")] + pub other_devices: Option>, +} + +impl UpdateDevicesTrustRequestModel { + pub fn new( + current_device: crate::models::DeviceKeysUpdateRequestModel, + ) -> UpdateDevicesTrustRequestModel { + UpdateDevicesTrustRequestModel { + master_password_hash: None, + otp: None, + auth_request_access_code: None, + secret: None, + current_device: Box::new(current_device), + other_devices: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/update_domains_request_model.rs b/crates/bitwarden-api-api/src/models/update_domains_request_model.rs index 9c651953d..7bf75cb8a 100644 --- a/crates/bitwarden-api-api/src/models/update_domains_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_domains_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateDomainsRequestModel { #[serde(rename = "equivalentDomains", skip_serializing_if = "Option::is_none")] pub equivalent_domains: Option>>, 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 155679c8b..c6b298251 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 @@ -8,37 +8,44 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateKeyRequestModel { #[serde(rename = "masterPasswordHash")] pub master_password_hash: String, - #[serde(rename = "ciphers")] - pub ciphers: Vec, - #[serde(rename = "folders")] - pub folders: Vec, - #[serde(rename = "sends", skip_serializing_if = "Option::is_none")] - pub sends: Option>, - #[serde(rename = "privateKey")] - pub private_key: String, #[serde(rename = "key")] pub key: String, + #[serde(rename = "privateKey")] + pub private_key: String, + #[serde(rename = "ciphers", skip_serializing_if = "Option::is_none")] + pub ciphers: Option>, + #[serde(rename = "folders", skip_serializing_if = "Option::is_none")] + pub folders: Option>, + #[serde(rename = "sends", skip_serializing_if = "Option::is_none")] + pub sends: Option>, + #[serde( + rename = "emergencyAccessKeys", + skip_serializing_if = "Option::is_none" + )] + pub emergency_access_keys: Option>, + #[serde(rename = "resetPasswordKeys", skip_serializing_if = "Option::is_none")] + pub reset_password_keys: Option>, } impl UpdateKeyRequestModel { pub fn new( master_password_hash: String, - ciphers: Vec, - folders: Vec, - private_key: String, key: String, + private_key: String, ) -> UpdateKeyRequestModel { UpdateKeyRequestModel { master_password_hash, - ciphers, - folders, - sends: None, - private_key, key, + private_key, + ciphers: None, + folders: None, + sends: None, + emergency_access_keys: None, + reset_password_keys: None, } } } diff --git a/crates/bitwarden-api-api/src/models/update_profile_request_model.rs b/crates/bitwarden-api-api/src/models/update_profile_request_model.rs index 06677e170..deb6a9d9e 100644 --- a/crates/bitwarden-api-api/src/models/update_profile_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_profile_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateProfileRequestModel { #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, diff --git a/crates/bitwarden-api-api/src/models/update_temp_password_request_model.rs b/crates/bitwarden-api-api/src/models/update_temp_password_request_model.rs index abc8b00ca..40eebaab9 100644 --- a/crates/bitwarden-api-api/src/models/update_temp_password_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_temp_password_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateTempPasswordRequestModel { #[serde(rename = "newMasterPasswordHash")] pub new_master_password_hash: String, 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 c4e267218..245993415 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateTwoFactorAuthenticatorRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, 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 4bab5acb9..55a168af3 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateTwoFactorDuoRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/update_two_factor_email_request_model.rs b/crates/bitwarden-api-api/src/models/update_two_factor_email_request_model.rs index 93328b7c8..1dc4f190c 100644 --- a/crates/bitwarden-api-api/src/models/update_two_factor_email_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_two_factor_email_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateTwoFactorEmailRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/update_two_factor_yubico_otp_request_model.rs b/crates/bitwarden-api-api/src/models/update_two_factor_yubico_otp_request_model.rs index eddf00bfc..19c5e37ba 100644 --- a/crates/bitwarden-api-api/src/models/update_two_factor_yubico_otp_request_model.rs +++ b/crates/bitwarden-api-api/src/models/update_two_factor_yubico_otp_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateTwoFactorYubicoOtpRequestModel { #[serde(rename = "masterPasswordHash", skip_serializing_if = "Option::is_none")] pub master_password_hash: Option, diff --git a/crates/bitwarden-api-api/src/models/uri_match_type.rs b/crates/bitwarden-api-api/src/models/uri_match_type.rs index f4ee450c1..6ccf82edb 100644 --- a/crates/bitwarden-api-api/src/models/uri_match_type.rs +++ b/crates/bitwarden-api-api/src/models/uri_match_type.rs @@ -14,29 +14,29 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum UriMatchType { - Variant0 = 0, - Variant1 = 1, - Variant2 = 2, - Variant3 = 3, - Variant4 = 4, - Variant5 = 5, + Domain = 0, + Host = 1, + StartsWith = 2, + Exact = 3, + RegularExpression = 4, + Never = 5, } impl ToString for UriMatchType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), - Self::Variant2 => String::from("2"), - Self::Variant3 => String::from("3"), - Self::Variant4 => String::from("4"), - Self::Variant5 => String::from("5"), + Self::Domain => String::from("0"), + Self::Host => String::from("1"), + Self::StartsWith => String::from("2"), + Self::Exact => String::from("3"), + Self::RegularExpression => String::from("4"), + Self::Never => String::from("5"), } } } impl Default for UriMatchType { fn default() -> UriMatchType { - Self::Variant0 + Self::Domain } } diff --git a/crates/bitwarden-api-api/src/models/user.rs b/crates/bitwarden-api-api/src/models/user.rs index e0ac14ed7..470a2837f 100644 --- a/crates/bitwarden-api-api/src/models/user.rs +++ b/crates/bitwarden-api-api/src/models/user.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct User { #[serde(rename = "email", skip_serializing_if = "Option::is_none")] pub email: Option, diff --git a/crates/bitwarden-api-api/src/models/user_key_response_model.rs b/crates/bitwarden-api-api/src/models/user_key_response_model.rs index 66bba4d5c..bb70b620d 100644 --- a/crates/bitwarden-api-api/src/models/user_key_response_model.rs +++ b/crates/bitwarden-api-api/src/models/user_key_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UserKeyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-api/src/models/user_license.rs b/crates/bitwarden-api-api/src/models/user_license.rs index 3ec9ef334..508222fb7 100644 --- a/crates/bitwarden-api-api/src/models/user_license.rs +++ b/crates/bitwarden-api-api/src/models/user_license.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UserLicense { #[serde(rename = "licenseKey", skip_serializing_if = "Option::is_none")] pub license_key: Option, diff --git a/crates/bitwarden-api-api/src/models/user_project_access_policy_response_model.rs b/crates/bitwarden-api-api/src/models/user_project_access_policy_response_model.rs index f4a6d96ca..280657ec1 100644 --- a/crates/bitwarden-api-api/src/models/user_project_access_policy_response_model.rs +++ b/crates/bitwarden-api-api/src/models/user_project_access_policy_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UserProjectAccessPolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -33,6 +33,8 @@ pub struct UserProjectAccessPolicyResponseModel { 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 { @@ -48,6 +50,7 @@ impl UserProjectAccessPolicyResponseModel { 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 index dada10c97..a69bb2a7c 100644 --- 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 @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UserServiceAccountAccessPolicyResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, @@ -36,6 +36,8 @@ pub struct UserServiceAccountAccessPolicyResponseModel { 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 { @@ -51,6 +53,7 @@ impl UserServiceAccountAccessPolicyResponseModel { organization_user_name: None, user_id: None, granted_service_account_id: None, + current_user: None, } } } diff --git a/crates/bitwarden-api-api/src/models/user_verification_requirement.rs b/crates/bitwarden-api-api/src/models/user_verification_requirement.rs new file mode 100644 index 000000000..647b1f9b9 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/user_verification_requirement.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 + */ + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum UserVerificationRequirement { + #[serde(rename = "required")] + Required, + #[serde(rename = "preferred")] + Preferred, + #[serde(rename = "discouraged")] + Discouraged, +} + +impl ToString for UserVerificationRequirement { + fn to_string(&self) -> String { + match self { + Self::Required => String::from("required"), + Self::Preferred => String::from("preferred"), + Self::Discouraged => String::from("discouraged"), + } + } +} + +impl Default for UserVerificationRequirement { + fn default() -> UserVerificationRequirement { + Self::Required + } +} diff --git a/crates/bitwarden-api-api/src/models/verify_delete_recover_request_model.rs b/crates/bitwarden-api-api/src/models/verify_delete_recover_request_model.rs index ac4ec4902..0ad0e127c 100644 --- a/crates/bitwarden-api-api/src/models/verify_delete_recover_request_model.rs +++ b/crates/bitwarden-api-api/src/models/verify_delete_recover_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct VerifyDeleteRecoverRequestModel { #[serde(rename = "userId")] pub user_id: String, diff --git a/crates/bitwarden-api-api/src/models/verify_email_request_model.rs b/crates/bitwarden-api-api/src/models/verify_email_request_model.rs index ce66c6f2c..74304bcca 100644 --- a/crates/bitwarden-api-api/src/models/verify_email_request_model.rs +++ b/crates/bitwarden-api-api/src/models/verify_email_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct VerifyEmailRequestModel { #[serde(rename = "userId")] pub user_id: String, diff --git a/crates/bitwarden-api-api/src/models/verify_otp_request_model.rs b/crates/bitwarden-api-api/src/models/verify_otp_request_model.rs index db57d5f60..6c9be6992 100644 --- a/crates/bitwarden-api-api/src/models/verify_otp_request_model.rs +++ b/crates/bitwarden-api-api/src/models/verify_otp_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct VerifyOtpRequestModel { #[serde(rename = "otp")] pub otp: String, diff --git a/crates/bitwarden-api-api/src/models/web_authn_credential_create_options_response_model.rs b/crates/bitwarden-api-api/src/models/web_authn_credential_create_options_response_model.rs new file mode 100644 index 000000000..f13024a6f --- /dev/null +++ b/crates/bitwarden-api-api/src/models/web_authn_credential_create_options_response_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnCredentialCreateOptionsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "options", skip_serializing_if = "Option::is_none")] + pub options: Option>, + #[serde(rename = "token", skip_serializing_if = "Option::is_none")] + pub token: Option, +} + +impl WebAuthnCredentialCreateOptionsResponseModel { + pub fn new() -> WebAuthnCredentialCreateOptionsResponseModel { + WebAuthnCredentialCreateOptionsResponseModel { + object: None, + options: None, + token: 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 new file mode 100644 index 000000000..852b3a527 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/web_authn_credential_response_model.rs @@ -0,0 +1,32 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnCredentialResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "name", skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "prfStatus", skip_serializing_if = "Option::is_none")] + pub prf_status: Option, +} + +impl WebAuthnCredentialResponseModel { + pub fn new() -> WebAuthnCredentialResponseModel { + WebAuthnCredentialResponseModel { + object: None, + id: None, + name: None, + prf_status: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/cipher_response_model_list_response_model.rs b/crates/bitwarden-api-api/src/models/web_authn_credential_response_model_list_response_model.rs similarity index 63% rename from crates/bitwarden-api-api/src/models/cipher_response_model_list_response_model.rs rename to crates/bitwarden-api-api/src/models/web_authn_credential_response_model_list_response_model.rs index ca2541930..b0072c62f 100644 --- a/crates/bitwarden-api-api/src/models/cipher_response_model_list_response_model.rs +++ b/crates/bitwarden-api-api/src/models/web_authn_credential_response_model_list_response_model.rs @@ -8,19 +8,19 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct CipherResponseModelListResponseModel { +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnCredentialResponseModelListResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, #[serde(rename = "data", skip_serializing_if = "Option::is_none")] - pub data: Option>, + pub data: Option>, #[serde(rename = "continuationToken", skip_serializing_if = "Option::is_none")] pub continuation_token: Option, } -impl CipherResponseModelListResponseModel { - pub fn new() -> CipherResponseModelListResponseModel { - CipherResponseModelListResponseModel { +impl WebAuthnCredentialResponseModelListResponseModel { + pub fn new() -> WebAuthnCredentialResponseModelListResponseModel { + WebAuthnCredentialResponseModelListResponseModel { object: None, data: None, continuation_token: None, diff --git a/crates/bitwarden-api-api/src/models/web_authn_login_assertion_options_response_model.rs b/crates/bitwarden-api-api/src/models/web_authn_login_assertion_options_response_model.rs new file mode 100644 index 000000000..27ed6afc4 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/web_authn_login_assertion_options_response_model.rs @@ -0,0 +1,29 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnLoginAssertionOptionsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "options", skip_serializing_if = "Option::is_none")] + pub options: Option>, + #[serde(rename = "token", skip_serializing_if = "Option::is_none")] + pub token: Option, +} + +impl WebAuthnLoginAssertionOptionsResponseModel { + pub fn new() -> WebAuthnLoginAssertionOptionsResponseModel { + WebAuthnLoginAssertionOptionsResponseModel { + object: None, + options: None, + token: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/web_authn_login_credential_create_request_model.rs b/crates/bitwarden-api-api/src/models/web_authn_login_credential_create_request_model.rs new file mode 100644 index 000000000..f5565655a --- /dev/null +++ b/crates/bitwarden-api-api/src/models/web_authn_login_credential_create_request_model.rs @@ -0,0 +1,49 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnLoginCredentialCreateRequestModel { + #[serde(rename = "deviceResponse")] + pub device_response: Box, + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "token")] + pub token: String, + #[serde(rename = "supportsPrf")] + pub supports_prf: bool, + #[serde(rename = "encryptedUserKey", skip_serializing_if = "Option::is_none")] + pub encrypted_user_key: Option, + #[serde(rename = "encryptedPublicKey", skip_serializing_if = "Option::is_none")] + pub encrypted_public_key: Option, + #[serde( + rename = "encryptedPrivateKey", + skip_serializing_if = "Option::is_none" + )] + pub encrypted_private_key: Option, +} + +impl WebAuthnLoginCredentialCreateRequestModel { + pub fn new( + device_response: crate::models::AuthenticatorAttestationRawResponse, + name: String, + token: String, + supports_prf: bool, + ) -> WebAuthnLoginCredentialCreateRequestModel { + WebAuthnLoginCredentialCreateRequestModel { + device_response: Box::new(device_response), + name, + token, + supports_prf, + encrypted_user_key: None, + encrypted_public_key: None, + encrypted_private_key: None, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/web_authn_login_credential_update_request_model.rs b/crates/bitwarden-api-api/src/models/web_authn_login_credential_update_request_model.rs new file mode 100644 index 000000000..ce12d0c43 --- /dev/null +++ b/crates/bitwarden-api-api/src/models/web_authn_login_credential_update_request_model.rs @@ -0,0 +1,41 @@ +/* + * Bitwarden Internal API + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: latest + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnLoginCredentialUpdateRequestModel { + #[serde(rename = "deviceResponse")] + pub device_response: Box, + #[serde(rename = "token")] + pub token: String, + #[serde(rename = "encryptedUserKey")] + pub encrypted_user_key: String, + #[serde(rename = "encryptedPublicKey")] + pub encrypted_public_key: String, + #[serde(rename = "encryptedPrivateKey")] + pub encrypted_private_key: String, +} + +impl WebAuthnLoginCredentialUpdateRequestModel { + pub fn new( + device_response: crate::models::AuthenticatorAssertionRawResponse, + token: String, + encrypted_user_key: String, + encrypted_public_key: String, + encrypted_private_key: String, + ) -> WebAuthnLoginCredentialUpdateRequestModel { + WebAuthnLoginCredentialUpdateRequestModel { + device_response: Box::new(device_response), + token, + encrypted_user_key, + encrypted_public_key, + encrypted_private_key, + } + } +} diff --git a/crates/bitwarden-api-api/src/models/bitwarden_product_type.rs b/crates/bitwarden-api-api/src/models/web_authn_prf_status.rs similarity index 55% rename from crates/bitwarden-api-api/src/models/bitwarden_product_type.rs rename to crates/bitwarden-api-api/src/models/web_authn_prf_status.rs index 2224e9781..074c402dc 100644 --- a/crates/bitwarden-api-api/src/models/bitwarden_product_type.rs +++ b/crates/bitwarden-api-api/src/models/web_authn_prf_status.rs @@ -13,22 +13,24 @@ #[derive( Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] -pub enum BitwardenProductType { - Variant0 = 0, - Variant1 = 1, +pub enum WebAuthnPrfStatus { + Enabled = 0, + Supported = 1, + Unsupported = 2, } -impl ToString for BitwardenProductType { +impl ToString for WebAuthnPrfStatus { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::Enabled => String::from("0"), + Self::Supported => String::from("1"), + Self::Unsupported => String::from("2"), } } } -impl Default for BitwardenProductType { - fn default() -> BitwardenProductType { - Self::Variant0 +impl Default for WebAuthnPrfStatus { + fn default() -> WebAuthnPrfStatus { + Self::Enabled } } diff --git a/crates/bitwarden-api-identity/.openapi-generator/FILES b/crates/bitwarden-api-identity/.openapi-generator/FILES index af978071e..968fd8fe4 100644 --- a/crates/bitwarden-api-identity/.openapi-generator/FILES +++ b/crates/bitwarden-api-identity/.openapi-generator/FILES @@ -7,10 +7,17 @@ src/apis/info_api.rs src/apis/mod.rs src/apis/sso_api.rs src/lib.rs +src/models/assertion_options.rs +src/models/authentication_extensions_client_inputs.rs +src/models/authenticator_transport.rs src/models/kdf_type.rs src/models/keys_request_model.rs src/models/mod.rs src/models/prelogin_request_model.rs src/models/prelogin_response_model.rs +src/models/public_key_credential_descriptor.rs +src/models/public_key_credential_type.rs src/models/register_request_model.rs src/models/register_response_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/.openapi-generator/VERSION b/crates/bitwarden-api-identity/.openapi-generator/VERSION index 4be2c727a..4b49d9bb6 100644 --- a/crates/bitwarden-api-identity/.openapi-generator/VERSION +++ b/crates/bitwarden-api-identity/.openapi-generator/VERSION @@ -1 +1 @@ -6.5.0 \ No newline at end of file +7.2.0 \ No newline at end of file diff --git a/crates/bitwarden-api-identity/Cargo.toml b/crates/bitwarden-api-identity/Cargo.toml index a74a43961..74d96d144 100644 --- a/crates/bitwarden-api-identity/Cargo.toml +++ b/crates/bitwarden-api-identity/Cargo.toml @@ -1,26 +1,29 @@ [package] name = "bitwarden-api-identity" -version = "0.2.1" -authors = ["Bitwarden Inc"] -license-file = "LICENSE" -repository = "https://github.com/bitwarden/sdk" -homepage = "https://bitwarden.com" description = """ Api bindings for the Bitwarden Identity API. """ -keywords = ["bitwarden"] categories = ["api-bindings"] -edition = "2018" + +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] -serde = "^1.0.163" -serde_derive = "^1.0.163" -serde_json = "^1.0.96" -serde_repr = "^0.1.12" -url = "^2.3.1" -uuid = { version = "^1.3.3", features = ["serde"] } +serde = ">=1.0.163, <2" +serde_derive = ">=1.0.163, <2" +serde_json = ">=1.0.96, <2" +serde_repr = ">=0.1.12, <0.2" +url = ">=2.3.1, <3" +uuid = { version = ">=1.3.3, <2", features = ["serde"] } [dependencies.reqwest] -version = "^0.11.18" -features = ["json", "multipart"] +version = ">=0.12, <0.13" +features = ["http2", "json", "multipart"] +default-features = false [dev-dependencies] diff --git a/crates/bitwarden-api-identity/LICENSE b/crates/bitwarden-api-identity/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden-api-identity/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden-api-identity/README.md b/crates/bitwarden-api-identity/README.md index 8288a18e5..bb190aaad 100644 --- a/crates/bitwarden-api-identity/README.md +++ b/crates/bitwarden-api-identity/README.md @@ -26,30 +26,38 @@ 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 | -| _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_ | [**account_external_callback_get**](docs/SsoApi.md#account_external_callback_get) | **GET** /account/ExternalCallback | -| _SsoApi_ | [**account_external_challenge_get**](docs/SsoApi.md#account_external_challenge_get) | **GET** /account/ExternalChallenge | -| _SsoApi_ | [**account_login_get**](docs/SsoApi.md#account_login_get) | **GET** /account/Login | -| _SsoApi_ | [**account_pre_validate_get**](docs/SsoApi.md#account_pre_validate_get) | **GET** /account/PreValidate | -| _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_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_ | [**account_external_callback_get**](docs/SsoApi.md#account_external_callback_get) | **GET** /account/ExternalCallback | +| _SsoApi_ | [**account_external_challenge_get**](docs/SsoApi.md#account_external_challenge_get) | **GET** /account/ExternalChallenge | +| _SsoApi_ | [**account_login_get**](docs/SsoApi.md#account_login_get) | **GET** /account/Login | +| _SsoApi_ | [**account_pre_validate_get**](docs/SsoApi.md#account_pre_validate_get) | **GET** /account/PreValidate | +| _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 +- [AssertionOptions](docs/AssertionOptions.md) +- [AuthenticationExtensionsClientInputs](docs/AuthenticationExtensionsClientInputs.md) +- [AuthenticatorTransport](docs/AuthenticatorTransport.md) - [KdfType](docs/KdfType.md) - [KeysRequestModel](docs/KeysRequestModel.md) - [PreloginRequestModel](docs/PreloginRequestModel.md) - [PreloginResponseModel](docs/PreloginResponseModel.md) +- [PublicKeyCredentialDescriptor](docs/PublicKeyCredentialDescriptor.md) +- [PublicKeyCredentialType](docs/PublicKeyCredentialType.md) - [RegisterRequestModel](docs/RegisterRequestModel.md) - [RegisterResponseModel](docs/RegisterResponseModel.md) +- [UserVerificationRequirement](docs/UserVerificationRequirement.md) +- [WebAuthnLoginAssertionOptionsResponseModel](docs/WebAuthnLoginAssertionOptionsResponseModel.md) To get access to the crate's generated documentation, use: diff --git a/crates/bitwarden-api-identity/src/apis/accounts_api.rs b/crates/bitwarden-api-identity/src/apis/accounts_api.rs index efffdae3d..c7b0bd1fd 100644 --- a/crates/bitwarden-api-identity/src/apis/accounts_api.rs +++ b/crates/bitwarden-api-identity/src/apis/accounts_api.rs @@ -27,6 +27,13 @@ pub enum AccountsRegisterPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`accounts_webauthn_assertion_options_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum AccountsWebauthnAssertionOptionsGetError { + UnknownValue(serde_json::Value), +} + pub async fn accounts_prelogin_post( configuration: &configuration::Configuration, prelogin_request_model: Option, @@ -102,3 +109,45 @@ pub async fn accounts_register_post( Err(Error::ResponseError(local_var_error)) } } + +pub async fn accounts_webauthn_assertion_options_get( + configuration: &configuration::Configuration, +) -> Result< + crate::models::WebAuthnLoginAssertionOptionsResponseModel, + Error, +> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/accounts/webauthn/assertion-options", + 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()); + } + + 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-identity/src/apis/configuration.rs b/crates/bitwarden-api-identity/src/apis/configuration.rs index 46cf9212e..6ed785dc7 100644 --- a/crates/bitwarden-api-identity/src/apis/configuration.rs +++ b/crates/bitwarden-api-identity/src/apis/configuration.rs @@ -8,8 +8,6 @@ * Generated by: https://openapi-generator.tech */ -use reqwest; - #[derive(Debug, Clone)] pub struct Configuration { pub base_path: String, diff --git a/crates/bitwarden-api-identity/src/apis/mod.rs b/crates/bitwarden-api-identity/src/apis/mod.rs index f50e7d44a..907fffa89 100644 --- a/crates/bitwarden-api-identity/src/apis/mod.rs +++ b/crates/bitwarden-api-identity/src/apis/mod.rs @@ -60,6 +60,37 @@ pub fn urlencode>(s: T) -> String { ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() } +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + } + serde_json::Value::String(s) => { + params.push((format!("{}[{}]", prefix, key), s.clone())) + } + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + pub mod accounts_api; pub mod info_api; pub mod sso_api; diff --git a/crates/bitwarden-api-identity/src/models/assertion_options.rs b/crates/bitwarden-api-identity/src/models/assertion_options.rs new file mode 100644 index 000000000..f69580f77 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/assertion_options.rs @@ -0,0 +1,44 @@ +/* + * 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 + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AssertionOptions { + #[serde(rename = "status", skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(rename = "errorMessage", skip_serializing_if = "Option::is_none")] + pub error_message: Option, + #[serde(rename = "challenge", skip_serializing_if = "Option::is_none")] + pub challenge: Option, + #[serde(rename = "timeout", skip_serializing_if = "Option::is_none")] + pub timeout: Option, + #[serde(rename = "rpId", skip_serializing_if = "Option::is_none")] + pub rp_id: Option, + #[serde(rename = "allowCredentials", skip_serializing_if = "Option::is_none")] + pub allow_credentials: Option>, + #[serde(rename = "userVerification", skip_serializing_if = "Option::is_none")] + pub user_verification: Option, + #[serde(rename = "extensions", skip_serializing_if = "Option::is_none")] + pub extensions: Option>, +} + +impl AssertionOptions { + pub fn new() -> AssertionOptions { + AssertionOptions { + status: None, + error_message: None, + challenge: None, + timeout: None, + rp_id: None, + allow_credentials: None, + user_verification: None, + extensions: None, + } + } +} diff --git a/crates/bitwarden-api-identity/src/models/authentication_extensions_client_inputs.rs b/crates/bitwarden-api-identity/src/models/authentication_extensions_client_inputs.rs new file mode 100644 index 000000000..2677278cc --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/authentication_extensions_client_inputs.rs @@ -0,0 +1,35 @@ +/* + * 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 + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AuthenticationExtensionsClientInputs { + #[serde(rename = "example.extension", skip_serializing_if = "Option::is_none")] + pub example_period_extension: Option, + #[serde(rename = "appid", skip_serializing_if = "Option::is_none")] + pub appid: Option, + #[serde(rename = "authnSel", skip_serializing_if = "Option::is_none")] + pub authn_sel: Option>, + #[serde(rename = "exts", skip_serializing_if = "Option::is_none")] + pub exts: Option, + #[serde(rename = "uvm", skip_serializing_if = "Option::is_none")] + pub uvm: Option, +} + +impl AuthenticationExtensionsClientInputs { + pub fn new() -> AuthenticationExtensionsClientInputs { + AuthenticationExtensionsClientInputs { + example_period_extension: None, + appid: None, + authn_sel: None, + exts: None, + uvm: None, + } + } +} diff --git a/crates/bitwarden-api-identity/src/models/authenticator_transport.rs b/crates/bitwarden-api-identity/src/models/authenticator_transport.rs new file mode 100644 index 000000000..bd3881305 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/authenticator_transport.rs @@ -0,0 +1,39 @@ +/* + * 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 + */ + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum AuthenticatorTransport { + #[serde(rename = "usb")] + Usb, + #[serde(rename = "nfc")] + Nfc, + #[serde(rename = "ble")] + Ble, + #[serde(rename = "internal")] + Internal, +} + +impl ToString for AuthenticatorTransport { + fn to_string(&self) -> String { + match self { + Self::Usb => String::from("usb"), + Self::Nfc => String::from("nfc"), + Self::Ble => String::from("ble"), + Self::Internal => String::from("internal"), + } + } +} + +impl Default for AuthenticatorTransport { + fn default() -> AuthenticatorTransport { + Self::Usb + } +} diff --git a/crates/bitwarden-api-identity/src/models/kdf_type.rs b/crates/bitwarden-api-identity/src/models/kdf_type.rs index 4b6acaef8..733fba272 100644 --- a/crates/bitwarden-api-identity/src/models/kdf_type.rs +++ b/crates/bitwarden-api-identity/src/models/kdf_type.rs @@ -14,21 +14,21 @@ Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize_repr, Deserialize_repr, )] pub enum KdfType { - Variant0 = 0, - Variant1 = 1, + PBKDF2_SHA256 = 0, + Argon2id = 1, } impl ToString for KdfType { fn to_string(&self) -> String { match self { - Self::Variant0 => String::from("0"), - Self::Variant1 => String::from("1"), + Self::PBKDF2_SHA256 => String::from("0"), + Self::Argon2id => String::from("1"), } } } impl Default for KdfType { fn default() -> KdfType { - Self::Variant0 + Self::PBKDF2_SHA256 } } diff --git a/crates/bitwarden-api-identity/src/models/keys_request_model.rs b/crates/bitwarden-api-identity/src/models/keys_request_model.rs index ebbb28145..579c08aed 100644 --- a/crates/bitwarden-api-identity/src/models/keys_request_model.rs +++ b/crates/bitwarden-api-identity/src/models/keys_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct KeysRequestModel { #[serde(rename = "publicKey", skip_serializing_if = "Option::is_none")] pub public_key: Option, diff --git a/crates/bitwarden-api-identity/src/models/mod.rs b/crates/bitwarden-api-identity/src/models/mod.rs index 0464a3ea9..715e29686 100644 --- a/crates/bitwarden-api-identity/src/models/mod.rs +++ b/crates/bitwarden-api-identity/src/models/mod.rs @@ -1,3 +1,9 @@ +pub mod assertion_options; +pub use self::assertion_options::AssertionOptions; +pub mod authentication_extensions_client_inputs; +pub use self::authentication_extensions_client_inputs::AuthenticationExtensionsClientInputs; +pub mod authenticator_transport; +pub use self::authenticator_transport::AuthenticatorTransport; pub mod kdf_type; pub use self::kdf_type::KdfType; pub mod keys_request_model; @@ -6,7 +12,15 @@ 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 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_request_model; pub use self::register_request_model::RegisterRequestModel; pub mod register_response_model; pub use self::register_response_model::RegisterResponseModel; +pub mod user_verification_requirement; +pub use self::user_verification_requirement::UserVerificationRequirement; +pub mod web_authn_login_assertion_options_response_model; +pub use self::web_authn_login_assertion_options_response_model::WebAuthnLoginAssertionOptionsResponseModel; diff --git a/crates/bitwarden-api-identity/src/models/prelogin_request_model.rs b/crates/bitwarden-api-identity/src/models/prelogin_request_model.rs index e56b1beb1..eb845142b 100644 --- a/crates/bitwarden-api-identity/src/models/prelogin_request_model.rs +++ b/crates/bitwarden-api-identity/src/models/prelogin_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PreloginRequestModel { #[serde(rename = "email")] pub email: String, diff --git a/crates/bitwarden-api-identity/src/models/prelogin_response_model.rs b/crates/bitwarden-api-identity/src/models/prelogin_response_model.rs index 583577ea6..a8935137d 100644 --- a/crates/bitwarden-api-identity/src/models/prelogin_response_model.rs +++ b/crates/bitwarden-api-identity/src/models/prelogin_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct PreloginResponseModel { #[serde(rename = "kdf", skip_serializing_if = "Option::is_none")] pub kdf: Option, diff --git a/crates/bitwarden-api-identity/src/models/public_key_credential_descriptor.rs b/crates/bitwarden-api-identity/src/models/public_key_credential_descriptor.rs new file mode 100644 index 000000000..67a4750ca --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/public_key_credential_descriptor.rs @@ -0,0 +1,29 @@ +/* + * 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 + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PublicKeyCredentialDescriptor { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub r#type: Option, + #[serde(rename = "id", skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "transports", skip_serializing_if = "Option::is_none")] + pub transports: Option>, +} + +impl PublicKeyCredentialDescriptor { + pub fn new() -> PublicKeyCredentialDescriptor { + PublicKeyCredentialDescriptor { + r#type: None, + id: None, + transports: None, + } + } +} diff --git a/crates/bitwarden-api-identity/src/models/public_key_credential_type.rs b/crates/bitwarden-api-identity/src/models/public_key_credential_type.rs new file mode 100644 index 000000000..85027f09e --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/public_key_credential_type.rs @@ -0,0 +1,30 @@ +/* + * 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 + */ + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum PublicKeyCredentialType { + #[serde(rename = "public-key")] + PublicKey, +} + +impl ToString for PublicKeyCredentialType { + fn to_string(&self) -> String { + match self { + Self::PublicKey => String::from("public-key"), + } + } +} + +impl Default for PublicKeyCredentialType { + fn default() -> PublicKeyCredentialType { + Self::PublicKey + } +} diff --git a/crates/bitwarden-api-identity/src/models/register_request_model.rs b/crates/bitwarden-api-identity/src/models/register_request_model.rs index 884702a6d..6034235a8 100644 --- a/crates/bitwarden-api-identity/src/models/register_request_model.rs +++ b/crates/bitwarden-api-identity/src/models/register_request_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RegisterRequestModel { #[serde(rename = "name", skip_serializing_if = "Option::is_none")] pub name: Option, diff --git a/crates/bitwarden-api-identity/src/models/register_response_model.rs b/crates/bitwarden-api-identity/src/models/register_response_model.rs index da2b4a79a..d2dbf37be 100644 --- a/crates/bitwarden-api-identity/src/models/register_response_model.rs +++ b/crates/bitwarden-api-identity/src/models/register_response_model.rs @@ -8,7 +8,7 @@ * Generated by: https://openapi-generator.tech */ -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct RegisterResponseModel { #[serde(rename = "object", skip_serializing_if = "Option::is_none")] pub object: Option, diff --git a/crates/bitwarden-api-identity/src/models/user_verification_requirement.rs b/crates/bitwarden-api-identity/src/models/user_verification_requirement.rs new file mode 100644 index 000000000..ca0ddadb0 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/user_verification_requirement.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 + */ + +/// +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum UserVerificationRequirement { + #[serde(rename = "required")] + Required, + #[serde(rename = "preferred")] + Preferred, + #[serde(rename = "discouraged")] + Discouraged, +} + +impl ToString for UserVerificationRequirement { + fn to_string(&self) -> String { + match self { + Self::Required => String::from("required"), + Self::Preferred => String::from("preferred"), + Self::Discouraged => String::from("discouraged"), + } + } +} + +impl Default for UserVerificationRequirement { + fn default() -> UserVerificationRequirement { + Self::Required + } +} diff --git a/crates/bitwarden-api-identity/src/models/web_authn_login_assertion_options_response_model.rs b/crates/bitwarden-api-identity/src/models/web_authn_login_assertion_options_response_model.rs new file mode 100644 index 000000000..199b12111 --- /dev/null +++ b/crates/bitwarden-api-identity/src/models/web_authn_login_assertion_options_response_model.rs @@ -0,0 +1,29 @@ +/* + * 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 + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct WebAuthnLoginAssertionOptionsResponseModel { + #[serde(rename = "object", skip_serializing_if = "Option::is_none")] + pub object: Option, + #[serde(rename = "options", skip_serializing_if = "Option::is_none")] + pub options: Option>, + #[serde(rename = "token", skip_serializing_if = "Option::is_none")] + pub token: Option, +} + +impl WebAuthnLoginAssertionOptionsResponseModel { + pub fn new() -> WebAuthnLoginAssertionOptionsResponseModel { + WebAuthnLoginAssertionOptionsResponseModel { + object: None, + options: None, + token: None, + } + } +} diff --git a/crates/bitwarden-c/Cargo.toml b/crates/bitwarden-c/Cargo.toml index 9181bedae..47d4ff5cd 100644 --- a/crates/bitwarden-c/Cargo.toml +++ b/crates/bitwarden-c/Cargo.toml @@ -1,10 +1,15 @@ [package] name = "bitwarden-c" version = "0.1.0" -edition = "2021" -rust-version = "1.57" +publish = false + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["lib", "staticlib", "cdylib"] bench = false @@ -15,4 +20,7 @@ tokio = { version = ">=1.28.2, <2.0", features = ["rt-multi-thread", "macros"] } bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } [dependencies] -env_logger = ">=0.10.0, <0.11" +env_logger = ">=0.10.0, <0.12" + +[lints] +workspace = true diff --git a/crates/bitwarden-c/LICENSE b/crates/bitwarden-c/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden-c/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs index 99180367b..32abe3dc0 100644 --- a/crates/bitwarden-c/src/c.rs +++ b/crates/bitwarden-c/src/c.rs @@ -8,11 +8,11 @@ use crate::{box_ptr, ffi_ref}; #[tokio::main] pub async extern "C" fn run_command( c_str_ptr: *const c_char, - client_ptr: *mut Client, + client_ptr: *const Client, ) -> *mut c_char { let client = unsafe { ffi_ref!(client_ptr) }; - let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr).to_bytes() }).unwrap(); - println!("{}", input_str); + let 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; match std::ffi::CString::new(result) { @@ -29,8 +29,8 @@ pub extern "C" fn init(c_str_ptr: *const c_char) -> *mut Client { if c_str_ptr.is_null() { box_ptr!(Client::new(None)) } else { - let input_string = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr).to_bytes() }) - .unwrap() + 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))) } diff --git a/crates/bitwarden-c/src/macros/ffi.rs b/crates/bitwarden-c/src/macros/ffi.rs index 3325838d1..d7384cd48 100644 --- a/crates/bitwarden-c/src/macros/ffi.rs +++ b/crates/bitwarden-c/src/macros/ffi.rs @@ -3,7 +3,7 @@ macro_rules! ffi_ref { ($name:ident) => {{ assert!(!$name.is_null()); - &mut *$name + &*$name }}; } diff --git a/crates/bitwarden-c/src/macros/mod.rs b/crates/bitwarden-c/src/macros/mod.rs index e524fcbf1..57ae9b9ee 100644 --- a/crates/bitwarden-c/src/macros/mod.rs +++ b/crates/bitwarden-c/src/macros/mod.rs @@ -1,3 +1 @@ -pub use ffi::*; - mod ffi; diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 606e24856..69a21704c 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -1,11 +1,20 @@ [package] name = "bitwarden-cli" -version = "0.1.0" -edition = "2021" -rust-version = "1.57" + +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] -clap = { version = "4.3.0", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" -supports-color = "2.0.0" +supports-color = "3.0.0" + +[lints] +workspace = true diff --git a/crates/bitwarden-cli/src/color.rs b/crates/bitwarden-cli/src/color.rs index 410a8b6ed..2e3a2c007 100644 --- a/crates/bitwarden-cli/src/color.rs +++ b/crates/bitwarden-cli/src/color.rs @@ -8,6 +8,9 @@ pub enum Color { } impl Color { + /** + * Evaluate if colors are supported + */ pub fn is_enabled(self) -> bool { match self { Color::No => false, @@ -17,6 +20,9 @@ impl Color { } } +/** + * Installs color_eyre, if Color is disabled we use an empty theme to disable error colors. + */ pub fn install_color_eyre(color: Color) -> color_eyre::Result<(), color_eyre::Report> { if color.is_enabled() { color_eyre::install() diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml new file mode 100644 index 000000000..e42ca686f --- /dev/null +++ b/crates/bitwarden-crypto/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "bitwarden-crypto" +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] +default = [] + +mobile = ["dep:uniffi"] # Mobile-specific features + +[dependencies] +aes = { version = ">=0.8.2, <0.9", features = ["zeroize"] } +argon2 = { version = ">=0.5.0, <0.6", features = [ + "std", + "zeroize", +], default-features = false } +base64 = ">=0.21.2, <0.22" +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" +hmac = ">=0.12.1, <0.13" +num-bigint = ">=0.4, <0.5" +num-traits = ">=0.2.15, <0.3" +pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } +rand = ">=0.8.5, <0.9" +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"] } +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.26.1", optional = true } +uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } +zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] } + +[dev-dependencies] +rand_chacha = "0.3.1" +serde_json = ">=1.0.96, <2.0" + +[lints] +workspace = true diff --git a/crates/bitwarden-crypto/README.md b/crates/bitwarden-crypto/README.md new file mode 100644 index 000000000..fd697aa3c --- /dev/null +++ b/crates/bitwarden-crypto/README.md @@ -0,0 +1,6 @@ +# Bitwarden Crypto + +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-crypto/src/aes.rs b/crates/bitwarden-crypto/src/aes.rs new file mode 100644 index 000000000..dde588563 --- /dev/null +++ b/crates/bitwarden-crypto/src/aes.rs @@ -0,0 +1,250 @@ +//! # AES operations +//! +//! Contains low level AES operations used by the rest of the library. +//! +//! In most cases you should use the [EncString][crate::EncString] with +//! [KeyEncryptable][crate::KeyEncryptable] & [KeyDecryptable][crate::KeyDecryptable] instead. + +use aes::cipher::{ + block_padding::Pkcs7, + typenum::{U16, U32}, + BlockDecryptMut, BlockEncryptMut, KeyIvInit, +}; +use generic_array::GenericArray; +use hmac::Mac; +use subtle::ConstantTimeEq; + +use crate::{ + error::{CryptoError, Result}, + util::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, +}; + +/// Decrypt using AES-256 in CBC mode. +/// +/// Behaves similar to [decrypt_aes256_hmac], but does not validate the MAC. +pub(crate) fn decrypt_aes256( + iv: &[u8; 16], + data: Vec, + key: &GenericArray, +) -> Result> { + // Decrypt data + let iv = GenericArray::from_slice(iv); + let mut data = data; + let decrypted_key_slice = cbc::Decryptor::::new(key, iv) + .decrypt_padded_mut::(&mut data) + .map_err(|_| CryptoError::KeyDecrypt)?; + + // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, + // we truncate to the subslice length + let decrypted_len = decrypted_key_slice.len(); + data.truncate(decrypted_len); + + Ok(data) +} + +/// Decrypt using AES-256 in CBC mode with MAC. +/// +/// Behaves similar to [decrypt_aes256], but also validates the MAC. +pub(crate) fn decrypt_aes256_hmac( + iv: &[u8; 16], + mac: &[u8; 32], + data: Vec, + mac_key: &GenericArray, + key: &GenericArray, +) -> Result> { + let res = generate_mac(mac_key, iv, &data)?; + if res.ct_ne(mac).into() { + return Err(CryptoError::InvalidMac); + } + decrypt_aes256(iv, data, key) +} + +/// Encrypt using AES-256 in CBC mode. +/// +/// Behaves similar to [encrypt_aes256_hmac], but does't generate a MAC. +/// +/// ## Returns +/// +/// A AesCbc256_B64 EncString +#[allow(unused)] +pub(crate) fn encrypt_aes256(data_dec: &[u8], key: &GenericArray) -> ([u8; 16], Vec) { + let rng = rand::thread_rng(); + let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); + + (iv, data) +} + +/// Encrypt using AES-256 in CBC mode with MAC. +/// +/// Behaves similar to [encrypt_aes256], but also generate a MAC. +/// +/// ## Returns +/// +/// A AesCbc256_HmacSha256_B64 EncString +pub(crate) fn encrypt_aes256_hmac( + data_dec: &[u8], + mac_key: &GenericArray, + key: &GenericArray, +) -> Result<([u8; 16], [u8; 32], Vec)> { + let rng = rand::thread_rng(); + let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); + let mac = generate_mac(mac_key, &iv, &data)?; + + Ok((iv, mac, data)) +} + +/// Encrypt using AES-256 in CBC mode. +/// +/// Used internally by: +/// - [encrypt_aes256] +/// - [encrypt_aes256_hmac] +fn encrypt_aes256_internal( + mut rng: impl rand::RngCore, + data_dec: &[u8], + key: &GenericArray, +) -> ([u8; 16], Vec) { + let mut iv = [0u8; 16]; + rng.fill_bytes(&mut iv); + let data = cbc::Encryptor::::new(key, &iv.into()) + .encrypt_padded_vec_mut::(data_dec); + + (iv, data) +} + +/// Decrypt using AES-128 in CBC mode. +/// +/// Behaves similar to [decrypt_aes128_hmac], but does not validate the MAC. +fn decrypt_aes128(iv: &[u8; 16], data: Vec, key: &GenericArray) -> Result> { + // Decrypt data + let iv = GenericArray::from_slice(iv); + let mut data = data; + let decrypted_key_slice = cbc::Decryptor::::new(key, iv) + .decrypt_padded_mut::(&mut data) + .map_err(|_| CryptoError::KeyDecrypt)?; + + // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, + // we truncate to the subslice length + let decrypted_len = decrypted_key_slice.len(); + data.truncate(decrypted_len); + + Ok(data) +} + +/// Decrypt using AES-128 in CBC mode with MAC. +/// +/// Behaves similar to [decrypt_aes128], but also validates the MAC. +pub fn decrypt_aes128_hmac( + iv: &[u8; 16], + mac: &[u8; 32], + data: Vec, + mac_key: &GenericArray, + key: &GenericArray, +) -> Result> { + let res = generate_mac(mac_key, iv, &data)?; + if res.ct_ne(mac).into() { + return Err(CryptoError::InvalidMac); + } + decrypt_aes128(iv, data, key) +} + +/// Generate a MAC using HMAC-SHA256. +fn generate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { + let mut hmac = + PbkdfSha256Hmac::new_from_slice(mac_key).expect("hmac new_from_slice should not fail"); + hmac.update(iv); + hmac.update(data); + let mac: [u8; PBKDF_SHA256_HMAC_OUT_SIZE] = (*hmac.finalize().into_bytes()) + .try_into() + .map_err(|_| CryptoError::InvalidMac)?; + + Ok(mac) +} + +#[cfg(test)] +mod tests { + use base64::{engine::general_purpose::STANDARD, Engine}; + use generic_array::{sequence::GenericSequence, ArrayLength}; + use rand::SeedableRng; + + use super::*; + + /// Helper function for generating a `GenericArray` of size 32 with each element being + /// a multiple of a given increment, starting from a given offset. + fn generate_generic_array>( + offset: u8, + increment: u8, + ) -> GenericArray { + GenericArray::generate(|i| offset + i as u8 * increment) + } + + /// Helper function for generating a vector of a given size with each element being + /// a multiple of a given increment, starting from a given offset. + fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec { + (0..length).map(|i| offset + i as u8 * increment).collect() + } + + #[test] + fn test_encrypt_aes256_internal() { + let key = generate_generic_array(0, 1); + + let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + let result = encrypt_aes256_internal(rng, "EncryptMe!".as_bytes(), &key); + assert_eq!( + result, + ( + [62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161], + vec![214, 76, 187, 97, 58, 146, 212, 140, 95, 164, 177, 204, 179, 133, 172, 148] + ) + ); + } + + #[test] + fn test_generate_mac() { + let mac_key = generate_vec(16, 0, 16); + + let iv = generate_vec(16, 0, 16); + let data = generate_vec(16, 0, 16); + + let result = generate_mac(&mac_key, &iv, &data); + + assert!(result.is_ok()); + let mac = result.unwrap(); + assert_eq!(mac.len(), 32); + } + + #[test] + fn test_decrypt_aes128() { + let iv = generate_vec(16, 0, 1); + let iv: &[u8; 16] = iv.as_slice().try_into().unwrap(); + let key = generate_generic_array(0, 1); + + let data = STANDARD.decode("dC0X+2IjFbeL4WLLg2jX7Q==").unwrap(); + + let decrypted = decrypt_aes128(iv, data, &key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } + + #[test] + fn test_decrypt_aes256() { + let iv = generate_vec(16, 0, 1); + let iv: &[u8; 16] = iv.as_slice().try_into().unwrap(); + let key = generate_generic_array(0, 1); + let data = STANDARD.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap(); + + let decrypted = decrypt_aes256(iv, data, &key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } + + #[test] + fn test_encrypt_decrypt_aes256() { + let key = generate_generic_array(0, 1); + let data = "EncryptMe!"; + + let (iv, encrypted) = encrypt_aes256(data.as_bytes(), &key); + let decrypted = decrypt_aes256(&iv, encrypted, &key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } +} diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs new file mode 100644 index 000000000..f9bda838a --- /dev/null +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -0,0 +1,324 @@ +use std::{fmt::Display, str::FromStr}; + +use base64::{engine::general_purpose::STANDARD, Engine}; +pub use internal::AsymmetricEncString; +use rsa::Oaep; +use serde::Deserialize; + +use super::{from_b64_vec, split_enc_string}; +use crate::{ + error::{CryptoError, EncStringParseError, Result}, + 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 { + /// # Encrypted string primitive + /// + /// [AsymmetricEncString] is a Bitwarden specific primitive that represents an asymmetrically + /// encrypted string. They are used together with the KeyDecryptable and KeyEncryptable + /// traits to encrypt and decrypt data using [crate::AsymmetricCryptoKey]s. + /// + /// The flexibility of the [AsymmetricEncString] type allows for different encryption algorithms + /// to be used which is represented by the different variants of the enum. + /// + /// ## Note + /// + /// For backwards compatibility we will rarely if ever be able to remove support for decrypting + /// old variants, but we should be opinionated in which variants are used for encrypting. + /// + /// ## Variants + /// - [Rsa2048_OaepSha256_B64](AsymmetricEncString::Rsa2048_OaepSha256_B64) + /// - [Rsa2048_OaepSha1_B64](AsymmetricEncString::Rsa2048_OaepSha1_B64) + /// + /// ## Serialization + /// + /// [AsymmetricEncString] implements [std::fmt::Display] and [std::str::FromStr] to allow for + /// easy serialization and uses a custom scheme to represent the different variants. + /// + /// The scheme is one of the following schemes: + /// - `[type].[data]` + /// + /// Where: + /// - `[type]`: is a digit number representing the variant. + /// - `[data]`: is the encrypted data. + #[derive(Clone, zeroize::ZeroizeOnDrop)] + #[allow(unused, non_camel_case_types)] + pub enum AsymmetricEncString { + /// 3 + Rsa2048_OaepSha256_B64 { data: Vec }, + /// 4 + Rsa2048_OaepSha1_B64 { data: Vec }, + /// 5 + #[deprecated] + Rsa2048_OaepSha256_HmacSha256_B64 { data: Vec, mac: Vec }, + /// 6 + #[deprecated] + Rsa2048_OaepSha1_HmacSha256_B64 { data: Vec, mac: Vec }, + } +} + +/// To avoid printing sensitive information, [AsymmetricEncString] debug prints to +/// `AsymmetricEncString`. +impl std::fmt::Debug for AsymmetricEncString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsymmetricEncString").finish() + } +} + +/// Deserializes an [AsymmetricEncString] from a string. +impl FromStr for AsymmetricEncString { + type Err = CryptoError; + + fn from_str(s: &str) -> Result { + let (enc_type, parts) = split_enc_string(s); + match (enc_type, parts.len()) { + ("3", 1) => { + let data = from_b64_vec(parts[0])?; + Ok(AsymmetricEncString::Rsa2048_OaepSha256_B64 { data }) + } + ("4", 1) => { + let data = from_b64_vec(parts[0])?; + Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data }) + } + #[allow(deprecated)] + ("5", 2) => { + let data = from_b64_vec(parts[0])?; + let mac: Vec = from_b64_vec(parts[1])?; + Ok(AsymmetricEncString::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac }) + } + #[allow(deprecated)] + ("6", 2) => { + let data = from_b64_vec(parts[0])?; + let mac: Vec = from_b64_vec(parts[1])?; + Ok(AsymmetricEncString::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac }) + } + + (enc_type, parts) => Err(EncStringParseError::InvalidTypeAsymm { + enc_type: enc_type.to_string(), + parts, + } + .into()), + } + } +} + +impl Display for AsymmetricEncString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let parts: Vec<&[u8]> = match self { + AsymmetricEncString::Rsa2048_OaepSha256_B64 { data } => vec![data], + AsymmetricEncString::Rsa2048_OaepSha1_B64 { data } => vec![data], + #[allow(deprecated)] + AsymmetricEncString::Rsa2048_OaepSha256_HmacSha256_B64 { data, mac } => vec![data, mac], + #[allow(deprecated)] + AsymmetricEncString::Rsa2048_OaepSha1_HmacSha256_B64 { data, mac } => vec![data, mac], + }; + + let encoded_parts: Vec = parts.iter().map(|part| STANDARD.encode(part)).collect(); + + write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; + + Ok(()) + } +} + +impl<'de> Deserialize<'de> for AsymmetricEncString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(super::FromStrVisitor::new()) + } +} + +impl serde::Serialize for AsymmetricEncString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl AsymmetricEncString { + /// Encrypt and produce a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. + pub fn encrypt_rsa2048_oaep_sha1( + data_dec: &[u8], + key: &dyn AsymmetricEncryptable, + ) -> Result { + let enc = encrypt_rsa2048_oaep_sha1(key.to_public_key(), data_dec)?; + Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data: enc }) + } + + /// The numerical representation of the encryption type of the [AsymmetricEncString]. + const fn enc_type(&self) -> u8 { + match self { + AsymmetricEncString::Rsa2048_OaepSha256_B64 { .. } => 3, + AsymmetricEncString::Rsa2048_OaepSha1_B64 { .. } => 4, + #[allow(deprecated)] + AsymmetricEncString::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5, + #[allow(deprecated)] + AsymmetricEncString::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6, + } + } +} + +impl KeyDecryptable> for AsymmetricEncString { + fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result> { + use AsymmetricEncString::*; + match self { + Rsa2048_OaepSha256_B64 { data } => key.key.decrypt(Oaep::new::(), data), + Rsa2048_OaepSha1_B64 { data } => key.key.decrypt(Oaep::new::(), data), + #[allow(deprecated)] + Rsa2048_OaepSha256_HmacSha256_B64 { data, .. } => { + key.key.decrypt(Oaep::new::(), data) + } + #[allow(deprecated)] + Rsa2048_OaepSha1_HmacSha256_B64 { data, .. } => { + key.key.decrypt(Oaep::new::(), data) + } + } + .map_err(|_| CryptoError::KeyDecrypt) + } +} + +impl KeyDecryptable for AsymmetricEncString { + fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result { + let dec: Vec = self.decrypt_with_key(key)?; + String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String) + } +} + +/// Usually we wouldn't want to expose AsymmetricEncStrings in the API or the schemas. +/// But during the transition phase we will expose endpoints using the AsymmetricEncString type. +impl schemars::JsonSchema for AsymmetricEncString { + fn schema_name() -> String { + "AsymmetricEncString".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + +#[cfg(test)] +mod tests { + use schemars::schema_for; + + use super::{AsymmetricCryptoKey, AsymmetricEncString, KeyDecryptable}; + + const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS +8HzYUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2 +e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86LnhD56A9FDUfuI0dVnPcrwNv0YJIo9 +4LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfa +F4/YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6A +QOajdZijfEvepgnOe7cQ7aeatiOJFrjTApKPGxOVRzEMX4XS4xbyhH0QxQeB6l16 +l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq +92qBuwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tP +dr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapjWpxEF+11x7r+wM+0xRZQ8sNFYG46a +PfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLX +UIh5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTR +buDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2 +hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxuc +vOUBeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjA +hCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIfTFKC/hDk6FKZlgwvupWYJyU9Rkyfs +tPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQY +UcUq4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vs +zv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVvq1UTXIeQcQnoY5lGHJl3K8mbS3TnX +E6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEP +jNX5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBez +MRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1eLLGd7YV0H+J3fgNc7gGWK51hOrF9 +JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXg +AoEZ18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGp +Is3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8+tPVgppLcG0+tMdLjigFQiDUQk2y3 +WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz +XKZBokBGnjFnTnKcs7nv/O8= +-----END PRIVATE KEY-----"; + + #[test] + fn test_enc_string_rsa2048_oaep_sha256_b64() { + let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let enc_str: &str = "3.YFqzW9LL/uLjCnl0RRLtndzGJ1FV27mcwQwGjfJPOVrgCX9nJSUYCCDd0iTIyOZ/zRxG47b6L1Z3qgkEfcxjmrSBq60gijc3E2TBMAg7OCLVcjORZ+i1sOVOudmOPWro6uA8refMrg4lqbieDlbLMzjVEwxfi5WpcL876cD0vYyRwvLO3bzFrsE7x33HHHtZeOPW79RqMn5efsB5Dj9wVheC9Ix9AYDjbo+rjg9qR6guwKmS7k2MSaIQlrDR7yu8LP+ePtiSjx+gszJV5jQGfcx60dtiLQzLS/mUD+RmU7B950Bpx0H7x56lT5yXZbWK5YkoP6qd8B8D2aKbP68Ywg=="; + let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + + assert_eq!(enc_string.enc_type(), 3); + + let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); + assert_eq!(res, "EncryptMe!"); + } + + #[test] + fn test_enc_string_rsa2048_oaep_sha1_b64() { + let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw=="; + let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + + assert_eq!(enc_string.enc_type(), 4); + + let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); + assert_eq!(res, "EncryptMe!"); + } + + #[test] + fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { + let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); + let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; + let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + + assert_eq!(enc_string.enc_type(), 6); + + let res: String = enc_string.decrypt_with_key(&private_key).unwrap(); + assert_eq!(res, "EncryptMe!"); + } + + #[test] + fn test_enc_string_serialization() { + #[derive(serde::Serialize, serde::Deserialize)] + struct Test { + key: AsymmetricEncString, + } + + let cipher = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; + let serialized = format!("{{\"key\":\"{cipher}\"}}"); + + let t = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(t.key.enc_type(), 6); + assert_eq!(t.key.to_string(), cipher); + assert_eq!(serde_json::to_string(&t).unwrap(), serialized); + } + + #[test] + fn test_from_str_invalid() { + let enc_str = "7.ABC"; + let enc_string: Result = enc_str.parse(); + + let err = enc_string.unwrap_err(); + assert_eq!( + err.to_string(), + "EncString error, Invalid asymmetric type, got type 7 with 1 parts" + ); + } + + #[test] + fn test_debug_format() { + let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw=="; + let enc_string: AsymmetricEncString = enc_str.parse().unwrap(); + + let debug_string = format!("{:?}", enc_string); + assert_eq!(debug_string, "AsymmetricEncString"); + } + + #[test] + fn test_json_schema() { + let schema = schema_for!(AsymmetricEncString); + + assert_eq!( + serde_json::to_string(&schema).unwrap(), + r#"{"$schema":"http://json-schema.org/draft-07/schema#","title":"AsymmetricEncString","type":"string"}"# + ); + } +} diff --git a/crates/bitwarden-crypto/src/enc_string/mod.rs b/crates/bitwarden-crypto/src/enc_string/mod.rs new file mode 100644 index 000000000..3250c1a58 --- /dev/null +++ b/crates/bitwarden-crypto/src/enc_string/mod.rs @@ -0,0 +1,130 @@ +/// Encrypted string types +mod asymmetric; +mod symmetric; + +use std::str::FromStr; + +pub use asymmetric::AsymmetricEncString; +use base64::{engine::general_purpose::STANDARD, Engine}; +pub use symmetric::EncString; + +use crate::error::{EncStringParseError, Result}; + +fn check_length(buf: &[u8], expected: usize) -> Result<()> { + if buf.len() < expected { + return Err(EncStringParseError::InvalidLength { + expected, + got: buf.len(), + } + .into()); + } + Ok(()) +} + +fn from_b64_vec(s: &str) -> Result> { + Ok(STANDARD + .decode(s) + .map_err(EncStringParseError::InvalidBase64)?) +} + +fn from_b64(s: &str) -> Result<[u8; N]> { + Ok(from_b64_vec(s)? + .try_into() + .map_err(|e: Vec<_>| EncStringParseError::InvalidLength { + expected: N, + got: e.len(), + })?) +} + +fn split_enc_string(s: &str) -> (&str, Vec<&str>) { + let header_parts: Vec<_> = s.split('.').collect(); + + if header_parts.len() == 2 { + (header_parts[0], header_parts[1].split('|').collect()) + } else { + // Support legacy format with no header + let parts: Vec<_> = s.split('|').collect(); + if parts.len() == 3 { + ("1", parts) // AesCbc128_HmacSha256_B64 + } else { + ("0", parts) // AesCbc256_B64 + } + } +} + +struct FromStrVisitor(std::marker::PhantomData); +impl FromStrVisitor { + fn new() -> Self { + Self(Default::default()) + } +} +impl serde::de::Visitor<'_> for FromStrVisitor +where + T::Err: std::fmt::Debug, +{ + type Value = T; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "a valid string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + T::from_str(v).map_err(|e| E::custom(format!("{:?}", e))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_check_length_less_than_expected() { + let buf = [1, 2, 3]; + let expected = 5; + let result = check_length(&buf, expected); + assert!(result.is_err()); + } + + #[test] + fn test_check_length_equal_to_expected() { + let buf = [1, 2, 3, 4, 5]; + let expected = 5; + let result = check_length(&buf, expected); + assert!(result.is_ok()); + } + + #[test] + fn test_check_length_greater_than_expected() { + let buf = [1, 2, 3, 4, 5, 6]; + let expected = 5; + let result = check_length(&buf, expected); + assert!(result.is_ok()); + } + + #[test] + fn test_split_enc_string_new_format() { + let s = "2.abc|def|ghi"; + let (header, parts) = split_enc_string(s); + assert_eq!(header, "2"); + assert_eq!(parts, vec!["abc", "def", "ghi"]); + } + + #[test] + fn test_split_enc_string_old_format_three_parts() { + let s = "abc|def|ghi"; + let (header, parts) = split_enc_string(s); + assert_eq!(header, "1"); + assert_eq!(parts, vec!["abc", "def", "ghi"]); + } + + #[test] + fn test_split_enc_string_old_format_fewer_parts() { + let s = "abc|def"; + let (header, parts) = split_enc_string(s); + assert_eq!(header, "0"); + assert_eq!(parts, vec!["abc", "def"]); + } +} diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs new file mode 100644 index 000000000..d312882d7 --- /dev/null +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -0,0 +1,449 @@ +use std::{fmt::Display, str::FromStr}; + +use aes::cipher::typenum::U32; +use base64::{engine::general_purpose::STANDARD, Engine}; +use generic_array::GenericArray; +use serde::Deserialize; + +use super::{check_length, from_b64, from_b64_vec, split_enc_string}; +use crate::{ + error::{CryptoError, EncStringParseError, Result}, + KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, +}; + +/// # Encrypted string primitive +/// +/// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted string. +/// They are are used together with the [KeyDecryptable] and [KeyEncryptable] traits to encrypt and +/// decrypt data using [SymmetricCryptoKey]s. +/// +/// The flexibility of the [EncString] type allows for different encryption algorithms to be used +/// which is represented by the different variants of the enum. +/// +/// ## Note +/// +/// For backwards compatibility we will rarely if ever be able to remove support for decrypting old +/// variants, but we should be opinionated in which variants are used for encrypting. +/// +/// ## Variants +/// - [AesCbc256_B64](EncString::AesCbc256_B64) +/// - [AesCbc128_HmacSha256_B64](EncString::AesCbc128_HmacSha256_B64) +/// - [AesCbc256_HmacSha256_B64](EncString::AesCbc256_HmacSha256_B64) +/// +/// ## Serialization +/// +/// [EncString] implements [Display] and [FromStr] to allow for easy serialization and uses a +/// custom scheme to represent the different variants. +/// +/// The scheme is one of the following schemes: +/// - `[type].[iv]|[data]` +/// - `[type].[iv]|[data]|[mac]` +/// +/// Where: +/// - `[type]`: is a digit number representing the variant. +/// - `[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)] +#[allow(unused, non_camel_case_types)] +pub enum EncString { + /// 0 + AesCbc256_B64 { iv: [u8; 16], data: Vec }, + /// 1 + AesCbc128_HmacSha256_B64 { + iv: [u8; 16], + mac: [u8; 32], + data: Vec, + }, + /// 2 + AesCbc256_HmacSha256_B64 { + iv: [u8; 16], + mac: [u8; 32], + data: Vec, + }, +} + +/// To avoid printing sensitive information, [EncString] debug prints to `EncString`. +impl std::fmt::Debug for EncString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EncString").finish() + } +} + +/// Deserializes an [EncString] from a string. +impl FromStr for EncString { + type Err = CryptoError; + + fn from_str(s: &str) -> Result { + let (enc_type, parts) = split_enc_string(s); + match (enc_type, parts.len()) { + ("0", 2) => { + let iv = from_b64(parts[0])?; + let data = from_b64_vec(parts[1])?; + + Ok(EncString::AesCbc256_B64 { iv, data }) + } + ("1" | "2", 3) => { + let iv = from_b64(parts[0])?; + let data = from_b64_vec(parts[1])?; + let mac = from_b64(parts[2])?; + + if enc_type == "1" { + Ok(EncString::AesCbc128_HmacSha256_B64 { iv, mac, data }) + } else { + Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + } + } + + (enc_type, parts) => Err(EncStringParseError::InvalidTypeSymm { + enc_type: enc_type.to_string(), + parts, + } + .into()), + } + } +} + +impl EncString { + /// Synthetic sugar for mapping `Option` to `Result>` + pub fn try_from_optional(s: Option) -> Result, CryptoError> { + s.map(|s| s.parse()).transpose() + } + + pub fn from_buffer(buf: &[u8]) -> Result { + if buf.is_empty() { + return Err(EncStringParseError::NoType.into()); + } + let enc_type = buf[0]; + + match enc_type { + 0 => { + check_length(buf, 18)?; + let iv = buf[1..17].try_into().expect("Valid length"); + let data = buf[17..].to_vec(); + + Ok(EncString::AesCbc256_B64 { iv, data }) + } + 1 | 2 => { + check_length(buf, 50)?; + let iv = buf[1..17].try_into().expect("Valid length"); + let mac = buf[17..49].try_into().expect("Valid length"); + let data = buf[49..].to_vec(); + + if enc_type == 1 { + Ok(EncString::AesCbc128_HmacSha256_B64 { iv, mac, data }) + } else { + Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + } + } + _ => Err(EncStringParseError::InvalidTypeSymm { + enc_type: enc_type.to_string(), + parts: 1, + } + .into()), + } + } + + pub fn to_buffer(&self) -> Result> { + let mut buf; + + match self { + EncString::AesCbc256_B64 { iv, data } => { + buf = Vec::with_capacity(1 + 16 + data.len()); + buf.push(self.enc_type()); + buf.extend_from_slice(iv); + buf.extend_from_slice(data); + } + EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } + | EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { + buf = Vec::with_capacity(1 + 16 + 32 + data.len()); + buf.push(self.enc_type()); + buf.extend_from_slice(iv); + buf.extend_from_slice(mac); + buf.extend_from_slice(data); + } + } + + Ok(buf) + } +} + +impl Display for EncString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let parts: Vec<&[u8]> = match self { + EncString::AesCbc256_B64 { iv, data } => vec![iv, data], + EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], + EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], + }; + + let encoded_parts: Vec = parts.iter().map(|part| STANDARD.encode(part)).collect(); + + write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; + + Ok(()) + } +} + +impl<'de> Deserialize<'de> for EncString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(super::FromStrVisitor::new()) + } +} + +impl serde::Serialize for EncString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl EncString { + pub fn encrypt_aes256_hmac( + data_dec: &[u8], + mac_key: &GenericArray, + key: &GenericArray, + ) -> Result { + let (iv, mac, data) = crate::aes::encrypt_aes256_hmac(data_dec, mac_key, key)?; + Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) + } + + /// The numerical representation of the encryption type of the [EncString]. + const fn enc_type(&self) -> u8 { + match self { + EncString::AesCbc256_B64 { .. } => 0, + EncString::AesCbc128_HmacSha256_B64 { .. } => 1, + EncString::AesCbc256_HmacSha256_B64 { .. } => 2, + } + } +} + +impl LocateKey for EncString {} +impl KeyEncryptable for &[u8] { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + EncString::encrypt_aes256_hmac( + self, + key.mac_key.as_ref().ok_or(CryptoError::InvalidMac)?, + &key.key, + ) + } +} + +impl KeyDecryptable> for EncString { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { + match self { + EncString::AesCbc256_B64 { iv, data } => { + let dec = crate::aes::decrypt_aes256(iv, data.clone(), &key.key)?; + Ok(dec) + } + EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } => { + // TODO: SymmetricCryptoKey is designed to handle 32 byte keys only, but this + // variant uses a 16 byte key This means the key+mac are going to be + // parsed as a single 32 byte key, at the moment we split it manually + // When refactoring the key handling, this should be fixed. + let enc_key = key.key[0..16].into(); + let mac_key = key.key[16..32].into(); + let dec = crate::aes::decrypt_aes128_hmac(iv, mac, data.clone(), mac_key, enc_key)?; + Ok(dec) + } + EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { + let mac_key = key.mac_key.as_ref().ok_or(CryptoError::InvalidMac)?; + let dec = + crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, &key.key)?; + Ok(dec) + } + } + } +} + +impl KeyEncryptable for String { + 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)?; + String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String) + } +} + +/// Usually we wouldn't want to expose EncStrings in the API or the schemas. +/// But during the transition phase we will expose endpoints using the EncString type. +impl schemars::JsonSchema for EncString { + fn schema_name() -> String { + "EncString".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + gen.subschema_for::() + } +} + +#[cfg(test)] +mod tests { + use schemars::schema_for; + + use super::EncString; + use crate::{ + derive_symmetric_key, KeyDecryptable, KeyEncryptable, SensitiveString, SymmetricCryptoKey, + }; + + #[test] + fn test_enc_string_roundtrip() { + let key = derive_symmetric_key("test"); + + let test_string = "encrypted_test_string"; + let cipher = test_string.to_owned().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)] + struct Test { + key: EncString, + } + + let cipher = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8="; + let serialized = format!("{{\"key\":\"{cipher}\"}}"); + + let t = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(t.key.enc_type(), 2); + assert_eq!(t.key.to_string(), cipher); + assert_eq!(serde_json::to_string(&t).unwrap(), serialized); + } + + #[test] + fn test_enc_from_to_buffer() { + let enc_str: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8="; + let enc_string: EncString = enc_str.parse().unwrap(); + + let enc_buf = enc_string.to_buffer().unwrap(); + + assert_eq!( + enc_buf, + vec![ + 2, 164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150, 67, + 163, 228, 185, 63, 138, 95, 246, 177, 174, 3, 125, 185, 176, 249, 2, 57, 54, 96, + 220, 49, 66, 72, 44, 221, 98, 76, 209, 45, 48, 180, 111, 93, 118, 241, 43, 16, 211, + 135, 233, 150, 136, 221, 71, 140, 125, 141, 215 + ] + ); + + let enc_string_new = EncString::from_buffer(&enc_buf).unwrap(); + + assert_eq!(enc_string_new.to_string(), enc_str) + } + + #[test] + fn test_from_str_cbc256() { + let enc_str = "0.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w=="; + let enc_string: EncString = enc_str.parse().unwrap(); + + assert_eq!(enc_string.enc_type(), 0); + if let EncString::AesCbc256_B64 { iv, data } = &enc_string { + assert_eq!( + iv, + &[164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150] + ); + assert_eq!( + data, + &[93, 118, 241, 43, 16, 211, 135, 233, 150, 136, 221, 71, 140, 125, 141, 215] + ); + } else { + panic!("Invalid variant") + }; + } + + #[test] + fn test_from_str_cbc128_hmac() { + let enc_str = "1.Hh8gISIjJCUmJygpKissLQ==|MjM0NTY3ODk6Ozw9Pj9AQUJDREU=|KCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkc="; + let enc_string: EncString = enc_str.parse().unwrap(); + + assert_eq!(enc_string.enc_type(), 1); + if let EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } = &enc_string { + assert_eq!( + iv, + &[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45] + ); + assert_eq!( + mac, + &[ + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71 + ] + ); + assert_eq!( + data, + &[50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69] + ); + } else { + panic!("Invalid variant") + }; + } + + #[test] + fn test_decrypt_cbc256() { + let key = SensitiveString::test("hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe08="); + let key = SymmetricCryptoKey::try_from(key).unwrap(); + + let enc_str = "0.NQfjHLr6za7VQVAbrpL81w==|wfrjmyJ0bfwkQlySrhw8dA=="; + let enc_string: EncString = enc_str.parse().unwrap(); + assert_eq!(enc_string.enc_type(), 0); + + let dec_str: String = enc_string.decrypt_with_key(&key).unwrap(); + assert_eq!(dec_str, "EncryptMe!"); + } + + #[test] + fn test_decrypt_cbc128_hmac() { + let key = SensitiveString::test("Gt1aZ8kTTgkF80bLtb7LiMZBcxEA2FA5mbvV4x7K208="); + let key = SymmetricCryptoKey::try_from(key).unwrap(); + + let enc_str = "1.CU/oG4VZuxbHoZSDZjCLQw==|kb1HGwAk+fQ275ORfLf5Ew==|8UaEYHyqRZcG37JWhYBOBdEatEXd1u1/wN7OuImolcM="; + let enc_string: EncString = enc_str.parse().unwrap(); + assert_eq!(enc_string.enc_type(), 1); + + let dec_str: String = enc_string.decrypt_with_key(&key).unwrap(); + assert_eq!(dec_str, "EncryptMe!"); + } + + #[test] + fn test_from_str_invalid() { + let enc_str = "7.ABC"; + let enc_string: Result = enc_str.parse(); + + let err = enc_string.unwrap_err(); + assert_eq!( + err.to_string(), + "EncString error, Invalid symmetric type, got type 7 with 1 parts" + ); + } + + #[test] + fn test_debug_format() { + let enc_str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8="; + let enc_string: EncString = enc_str.parse().unwrap(); + + let debug_string = format!("{:?}", enc_string); + assert_eq!(debug_string, "EncString"); + } + + #[test] + fn test_json_schema() { + let schema = schema_for!(EncString); + + assert_eq!( + serde_json::to_string(&schema).unwrap(), + r#"{"$schema":"http://json-schema.org/draft-07/schema#","title":"EncString","type":"string"}"# + ); + } +} diff --git a/crates/bitwarden-crypto/src/encryptable.rs b/crates/bitwarden-crypto/src/encryptable.rs new file mode 100644 index 000000000..a9882629f --- /dev/null +++ b/crates/bitwarden-crypto/src/encryptable.rs @@ -0,0 +1,95 @@ +use std::{collections::HashMap, hash::Hash}; + +use rayon::prelude::*; +use uuid::Uuid; + +use crate::{CryptoError, KeyDecryptable, KeyEncryptable, Result, SymmetricCryptoKey}; + +pub trait KeyContainer: Send + Sync { + fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey>; +} + +pub trait LocateKey { + fn locate_key<'a>( + &self, + enc: &'a dyn KeyContainer, + org_id: &Option, + ) -> Option<&'a SymmetricCryptoKey> { + enc.get_key(org_id) + } +} + +/// Deprecated: please use LocateKey and KeyDecryptable instead +pub trait Encryptable { + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result; +} + +/// Deprecated: please use LocateKey and KeyDecryptable instead +pub trait Decryptable { + fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option) -> Result; +} + +impl + LocateKey, Output> Encryptable for T { + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result { + let key = self + .locate_key(enc, org_id) + .ok_or(CryptoError::MissingKey)?; + self.encrypt_with_key(key) + } +} + +impl + LocateKey, Output> Decryptable for T { + fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option) -> Result { + let key = self + .locate_key(enc, org_id) + .ok_or(CryptoError::MissingKey)?; + self.decrypt_with_key(key) + } +} + +impl + Send + Sync, Output: Send + Sync> Encryptable> + for Vec +{ + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result> { + self.into_par_iter() + .map(|e| e.encrypt(enc, org_id)) + .collect() + } +} + +impl + Send + Sync, Output: Send + Sync> Decryptable> + for Vec +{ + fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option) -> Result> { + self.into_par_iter() + .map(|e| e.decrypt(enc, org_id)) + .collect() + } +} + +impl + Send + Sync, Output: Send + Sync, Id: Hash + Eq + Send + Sync> + Encryptable> for HashMap +{ + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result> { + self.into_par_iter() + .map(|(id, e)| Ok((id, e.encrypt(enc, org_id)?))) + .collect() + } +} + +impl< + T: Decryptable + Send + Sync, + Output: Send + Sync, + Id: Hash + Eq + Copy + Send + Sync, + > Decryptable> for HashMap +{ + fn decrypt( + &self, + enc: &dyn KeyContainer, + org_id: &Option, + ) -> Result> { + self.into_par_iter() + .map(|(id, e)| Ok((*id, e.decrypt(enc, org_id)?))) + .collect() + } +} diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs new file mode 100644 index 000000000..7cfb354d7 --- /dev/null +++ b/crates/bitwarden-crypto/src/error.rs @@ -0,0 +1,63 @@ +use std::fmt::Debug; + +use thiserror::Error; + +use crate::fingerprint::FingerprintError; + +#[derive(Debug, Error)] +pub enum CryptoError { + #[error("The provided key is not the expected type")] + InvalidKey, + #[error("The cipher's MAC doesn't match the expected value")] + InvalidMac, + #[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("EncString error, {0}")] + EncString(#[from] EncStringParseError), + + #[error("Rsa error, {0}")] + RsaError(#[from] RsaError), + + #[error("Fingerprint error, {0}")] + FingerprintError(#[from] FingerprintError), + + #[error("Argon2 error, {0}")] + ArgonError(#[from] argon2::Error), + + #[error("Number is zero")] + ZeroNumber, +} + +#[derive(Debug, Error)] +pub enum EncStringParseError { + #[error("No type detected, missing '.' separator")] + NoType, + #[error("Invalid symmetric type, got type {enc_type} with {parts} parts")] + InvalidTypeSymm { enc_type: String, parts: usize }, + #[error("Invalid asymmetric type, got type {enc_type} with {parts} parts")] + InvalidTypeAsymm { enc_type: String, parts: usize }, + #[error("Error decoding base64: {0}")] + InvalidBase64(#[from] base64::DecodeError), + #[error("Invalid length: expected {expected}, got {got}")] + InvalidLength { expected: usize, got: usize }, +} + +#[derive(Debug, Error)] +pub enum RsaError { + #[error("Unable to create public key")] + CreatePublicKey, + #[error("Unable to create private key")] + CreatePrivateKey, + #[error("Rsa error, {0}")] + Rsa(#[from] rsa::Error), +} + +/// Alias for `Result`. +pub(crate) type Result = std::result::Result; diff --git a/crates/bitwarden/src/crypto/fingerprint.rs b/crates/bitwarden-crypto/src/fingerprint.rs similarity index 68% rename from crates/bitwarden/src/crypto/fingerprint.rs rename to crates/bitwarden-crypto/src/fingerprint.rs index 9473a2cda..b88435dbc 100644 --- a/crates/bitwarden/src/crypto/fingerprint.rs +++ b/crates/bitwarden-crypto/src/fingerprint.rs @@ -1,27 +1,37 @@ +//! # Fingerprint +//! +//! Provides a way to derive fingerprints from fingerprint material and public keys. This is most +//! commonly used for account fingerprints, where the fingerprint material is the user's id and the +//! public key is the user's public key. + use num_bigint::BigUint; use num_traits::cast::ToPrimitive; use sha2::Digest; +use thiserror::Error; -use crate::{ - error::{Error, Result}, - wordlist::EFF_LONG_WORD_LIST, -}; +use crate::{error::Result, wordlist::EFF_LONG_WORD_LIST, CryptoError}; -pub(crate) fn fingerprint(fingerprint_material: &str, public_key: &[u8]) -> Result { +/// Computes a fingerprint of the given `fingerprint_material` using the given `public_key`. +/// +/// This is commonly used for account fingerprints. With the following arguments: +/// - `fingerprint_material`: user's id. +/// - `public_key`: user's public key. +pub fn fingerprint(fingerprint_material: &str, public_key: &[u8]) -> Result { let mut h = sha2::Sha256::new(); h.update(public_key); h.finalize(); - let hkdf = hkdf::Hkdf::::from_prk(public_key) - .map_err(|_| Error::Internal("hkdf::InvalidLength"))?; + let hkdf = + hkdf::Hkdf::::from_prk(public_key).map_err(|_| CryptoError::InvalidKeyLen)?; let mut user_fingerprint = [0u8; 32]; hkdf.expand(fingerprint_material.as_bytes(), &mut user_fingerprint) - .map_err(|_| Error::Internal("hkdf::expand"))?; + .map_err(|_| CryptoError::InvalidKeyLen)?; - Ok(hash_word(user_fingerprint).unwrap()) + hash_word(user_fingerprint) } +/// Derive a 5 word phrase from a 32 byte hash. fn hash_word(hash: [u8; 32]) -> Result { let minimum_entropy = 64; @@ -31,9 +41,7 @@ fn hash_word(hash: [u8; 32]) -> Result { let hash_arr: Vec = hash.to_vec(); let entropy_available = hash_arr.len() * 4; if num_words as f64 * entropy_per_word > entropy_available as f64 { - return Err(Error::Internal( - "Output entropy of hash function is too small", - )); + return Err(FingerprintError::EntropyTooSmall.into()); } let mut phrase = Vec::new(); @@ -43,12 +51,21 @@ fn hash_word(hash: [u8; 32]) -> Result { let remainder = hash_number.clone() % EFF_LONG_WORD_LIST.len(); hash_number /= EFF_LONG_WORD_LIST.len(); - phrase.push(EFF_LONG_WORD_LIST[remainder.to_usize().unwrap()].to_string()); + let index = remainder + .to_usize() + .expect("Remainder is less than EFF_LONG_WORD_LIST.len()"); + phrase.push(EFF_LONG_WORD_LIST[index].to_string()); } Ok(phrase.join("-")) } +#[derive(Debug, Error)] +pub enum FingerprintError { + #[error("Entropy is too small")] + EntropyTooSmall, +} + #[cfg(test)] mod tests { use super::fingerprint; diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs new file mode 100644 index 000000000..be523bbc6 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -0,0 +1,225 @@ +use std::pin::Pin; + +use rsa::{pkcs8::DecodePublicKey, RsaPrivateKey, RsaPublicKey}; + +use super::key_encryptable::CryptoKey; +use crate::error::{CryptoError, Result}; + +/// Trait to allow both [`AsymmetricCryptoKey`] and [`AsymmetricPublicCryptoKey`] to be used to +/// encrypt [AsymmetricEncString](crate::AsymmetricEncString). +pub trait AsymmetricEncryptable { + fn to_public_key(&self) -> &RsaPublicKey; +} + +/// An asymmetric public encryption key. Can only encrypt +/// [AsymmetricEncString](crate::AsymmetricEncString), usually accompanied by a +/// [AsymmetricCryptoKey] +pub struct AsymmetricPublicCryptoKey { + pub(crate) key: RsaPublicKey, +} + +impl AsymmetricPublicCryptoKey { + /// Build a public key from the SubjectPublicKeyInfo DER. + pub fn from_der(der: &[u8]) -> Result { + Ok(Self { + key: rsa::RsaPublicKey::from_public_key_der(der) + .map_err(|_| CryptoError::InvalidKey)?, + }) + } +} + +impl AsymmetricEncryptable for AsymmetricPublicCryptoKey { + fn to_public_key(&self) -> &RsaPublicKey { + &self.key + } +} + +/// An asymmetric encryption key. Contains both the public and private key. Can be used to both +/// encrypt and decrypt [`AsymmetricEncString`](crate::AsymmetricEncString). +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, + // we use a Box to keep the values on the heap. We also pin the box to make sure + // that the contents can't be pulled out of the box and moved + pub(crate) key: Pin>, +} + +// Note that RsaPrivateKey already implements ZeroizeOnDrop, so we don't need to do anything +// We add this assertion to make sure that this is still true in the future +const _: () = { + fn assert_zeroize_on_drop() {} + fn assert_all() { + assert_zeroize_on_drop::(); + } +}; + +impl zeroize::ZeroizeOnDrop for AsymmetricCryptoKey {} + +impl AsymmetricCryptoKey { + /// Generate a random AsymmetricCryptoKey (RSA-2048). + pub fn generate(rng: &mut R) -> Self { + let bits = 2048; + + Self { + key: Box::pin(RsaPrivateKey::new(rng, bits).expect("failed to generate a key")), + } + } + + pub fn from_pem(pem: &str) -> Result { + use rsa::pkcs8::DecodePrivateKey; + Ok(Self { + key: Box::pin(RsaPrivateKey::from_pkcs8_pem(pem).map_err(|_| CryptoError::InvalidKey)?), + }) + } + + pub fn from_der(der: &[u8]) -> Result { + use rsa::pkcs8::DecodePrivateKey; + Ok(Self { + key: Box::pin(RsaPrivateKey::from_pkcs8_der(der).map_err(|_| CryptoError::InvalidKey)?), + }) + } + + pub fn to_der(&self) -> Result> { + use rsa::pkcs8::EncodePrivateKey; + Ok(self + .key + .to_pkcs8_der() + .map_err(|_| CryptoError::InvalidKey)? + .as_bytes() + .to_owned()) + } + + pub fn to_public_der(&self) -> Result> { + use rsa::pkcs8::EncodePublicKey; + Ok(self + .to_public_key() + .to_public_key_der() + .map_err(|_| CryptoError::InvalidKey)? + .as_bytes() + .to_owned()) + } +} + +impl AsymmetricEncryptable for AsymmetricCryptoKey { + fn to_public_key(&self) -> &RsaPublicKey { + (*self.key).as_ref() + } +} + +impl CryptoKey for AsymmetricCryptoKey {} + +// We manually implement these to make sure we don't print any sensitive data +impl std::fmt::Debug for AsymmetricCryptoKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AsymmetricCryptoKey").finish() + } +} + +#[cfg(test)] +mod tests { + use base64::{engine::general_purpose::STANDARD, Engine}; + + use crate::{ + AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, KeyDecryptable, + }; + + #[test] + fn test_asymmetric_crypto_key() { + let pem_key_str = "-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5 +qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc +afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm +qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv +b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw +P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam +rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi +szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx +0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+ +8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR +jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach +vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI +1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR +J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7 +l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ +TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9 +ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye +KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN +wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ +UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA +kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W +pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN +Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi +CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup +PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf +DnqOsltgPomWZ7xVfMkm9niL2OA= +-----END PRIVATE KEY-----"; + + let der_key_vec = STANDARD.decode("MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYcafeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4CwmqqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyvb0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZwP7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2famrEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKiszJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVRjB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKachvGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KRJ30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQTjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9ByeKvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiNwEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZUZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEAkY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7Wpt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwNZy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLiCVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzupPFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnfDnqOsltgPomWZ7xVfMkm9niL2OA=").unwrap(); + + // Load the two different formats and check they are the same key + let pem_key = AsymmetricCryptoKey::from_pem(pem_key_str).unwrap(); + let der_key = AsymmetricCryptoKey::from_der(&der_key_vec).unwrap(); + assert_eq!(pem_key.key, der_key.key); + + // Check that the keys can be converted back to DER + assert_eq!(der_key.to_der().unwrap(), der_key_vec); + assert_eq!(pem_key.to_der().unwrap(), der_key_vec); + } + + #[test] + fn test_encrypt_public_decrypt_private() { + let private_key = STANDARD + .decode(concat!( + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH", + "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY", + "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL", + "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK", + "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2", + "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO", + "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ", + "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj", + "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B", + "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL", + "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4", + "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk", + "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro", + "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv", + "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH", + "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j", + "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU", + "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM", + "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC", + "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G", + "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/", + "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J", + "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94", + "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs", + "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R", + "GDgRMUB6cL3IRVzcR0dC6cY=", + )) + .unwrap(); + + let public_key = STANDARD + .decode(concat!( + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc", + "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X", + "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd", + "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui", + "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK", + "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS", + "XQIDAQAB", + )) + .unwrap(); + + let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); + let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); + + let plaintext = "Hello, world!"; + let encrypted = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(plaintext.as_bytes(), &public_key) + .unwrap(); + let decrypted: String = encrypted.decrypt_with_key(&private_key).unwrap(); + + assert_eq!(plaintext, decrypted); + } +} diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs new file mode 100644 index 000000000..d33e0dc32 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -0,0 +1,141 @@ +use crate::{ + error::Result, AsymmetricCryptoKey, AsymmetricEncString, CryptoError, DecryptedVec, EncString, + KeyDecryptable, KeyEncryptable, SensitiveString, SymmetricCryptoKey, +}; + +/// Device Key +/// +/// Encrypts the DevicePrivateKey +/// Allows the device to decrypt the UserKey, via the DevicePrivateKey. +#[derive(Debug)] +pub struct DeviceKey(SymmetricCryptoKey); + +#[derive(Debug)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct TrustDeviceResponse { + /// Base64 encoded device key + pub device_key: SensitiveString, + /// UserKey encrypted with DevicePublicKey + pub protected_user_key: AsymmetricEncString, + /// DevicePrivateKey encrypted with [DeviceKey] + pub protected_device_private_key: EncString, + /// DevicePublicKey encrypted with [UserKey](super::UserKey) + pub protected_device_public_key: EncString, +} + +impl DeviceKey { + /// Generate a new device key + /// + /// Note: Input has to be a SymmetricCryptoKey instead of UserKey because that's what we get + /// from EncSettings. + pub fn trust_device(user_key: &SymmetricCryptoKey) -> Result { + let mut rng = rand::thread_rng(); + let device_key = DeviceKey(SymmetricCryptoKey::generate(&mut rng)); + + let device_private_key = AsymmetricCryptoKey::generate(&mut rng); + + // Encrypt both the key and mac_key of the user key + let data = user_key.to_vec(); + + let protected_user_key = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(data.expose(), &device_private_key)?; + + let protected_device_public_key = device_private_key + .to_public_der()? + .encrypt_with_key(user_key)?; + + let protected_device_private_key = device_private_key + .to_der()? + .encrypt_with_key(&device_key.0)?; + + Ok(TrustDeviceResponse { + device_key: device_key.to_base64(), + protected_user_key, + protected_device_private_key, + protected_device_public_key, + }) + } + + /// Decrypt the user key using the device key + pub fn decrypt_user_key( + &self, + protected_device_private_key: EncString, + protected_user_key: AsymmetricEncString, + ) -> Result { + let device_private_key: Vec = protected_device_private_key.decrypt_with_key(&self.0)?; + let device_private_key = AsymmetricCryptoKey::from_der(&device_private_key)?; + + let dec: Vec = protected_user_key.decrypt_with_key(&device_private_key)?; + let dec = DecryptedVec::new(Box::new(dec)); + let user_key = SymmetricCryptoKey::try_from(dec)?; + + Ok(user_key) + } + + fn to_base64(&self) -> SensitiveString { + self.0.to_base64() + } +} + +impl TryFrom for DeviceKey { + type Error = CryptoError; + + fn try_from(value: SensitiveString) -> Result { + SymmetricCryptoKey::try_from(value).map(DeviceKey) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::derive_symmetric_key; + + #[test] + fn test_trust_device() { + let key = derive_symmetric_key("test"); + + let result = DeviceKey::trust_device(&key).unwrap(); + + let device_key = DeviceKey::try_from(result.device_key).unwrap(); + let decrypted = device_key + .decrypt_user_key( + result.protected_device_private_key, + result.protected_user_key, + ) + .unwrap(); + + assert_eq!(key.key, decrypted.key); + assert_eq!(key.mac_key, decrypted.mac_key); + } + + #[test] + fn test_decrypt_user_key() { + // Example keys from desktop app + let user_key: &mut [u8] = &mut [ + 109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216, 212, + 173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91, 215, 21, + 128, 91, 226, 222, 165, 67, 251, 34, 83, 81, 77, 147, 225, 76, 13, 41, 102, 45, 183, + 218, 106, 89, 254, 208, 251, 101, 130, 10, + ]; + let user_key = SymmetricCryptoKey::try_from(user_key).unwrap(); + + let key_data: &mut [u8] = &mut [ + 114, 235, 60, 115, 172, 156, 203, 145, 195, 130, 215, 250, 88, 146, 215, 230, 12, 109, + 245, 222, 54, 217, 255, 211, 221, 105, 230, 236, 65, 52, 209, 133, 76, 208, 113, 254, + 194, 216, 156, 19, 230, 62, 32, 93, 87, 7, 144, 156, 117, 142, 250, 32, 182, 118, 187, + 8, 247, 7, 203, 201, 65, 147, 206, 247, + ]; + let device_key = DeviceKey(key_data.try_into().unwrap()); + + let protected_user_key: AsymmetricEncString = "4.f+VbbacRhO2q4MOUSdt1AIjQ2FuLAvg4aDxJMXAh3VxvbmUADj8Ct/R7XEpPUqApmbRS566jS0eRVy8Sk08ogoCdj1IFN9VsIky2i2X1WHK1fUnr3UBmXE3tl2NPBbx56U+h73S2jNTSyet2W18Jg2q7/w8KIhR3J41QrG9aGoOTN93to3hb5W4z6rdrSI0e7GkizbwcIA0NH7Z1JyAhrjPm9+tjRjg060YbEbGaWTAOkZWfgbLjr8bY455DteO2xxG139cOx7EBo66N+YhjsLi0ozkeUyPQkoWBdKMcQllS7jCfB4fDyJA05ALTbk74syKkvqFxqwmQbg+aVn+dcw==".parse().unwrap(); + + let protected_device_private_key: EncString = "2.GyQfUYWW6Byy4UV5icFLxg==|EMiU7OTF79N6tfv3+YUs5zJhBAgqv6sa5YCoPl6yAETh7Tfk+JmbeizxXFPj5Q1X/tcVpDZl/3fGcxtnIxg1YtvDFn7j8uPnoApOWhCKmwcvJSIkt+qvX3lELNBwZXozSiy7PbQ0JbCMe2d4MkimR5k8+lE9FB3208yYK7nOJhlrsUCnOekCYEU9/4NCMA8tz8SpITx/MN4JJ1TQ/KjPJYLt+3JNUxK47QlgREWQvyVzCRt7ZGtcgIJ/U1qycAWMpEg9NkuV8j5QRA1S7VBsA6qliJwys5+dmTuIOmOMwdKFZDc4ZvWoRkPp2TSJBu7L8sSAgU6mmDWac8iQ+9Ka/drdfwYLrH8GAZvURk79tSpRrT7+PAFe2QdUtliUIyiqkh8iJVjZube4hRnEsRuX9V9b+UdtAr6zAj7mugO/VAu5T9J38V79V2ohG3NtXysDeKLXpAlkhjllWXeq/wret2fD4WiwqEDj0G2A/PY3F3OziIgp0UKc00AfqrPq8OVK3A+aowwVqdYadgxyoVCKWJ8unJeAXG7MrMQ9tHpzF6COoaEy7Wwoc17qko33zazwLZbfAjB4oc8Ea26jRKnJZP56sVZAjOSQQMziAsA08MRaa/DQhgRea1+Ygba0gMft8Dww8anN2gQBveTZRBWyqXYgN3U0Ity5gNauT8RnFk9faqVFt2Qxnp0JgJ+PsqEt5Hn4avBRZQQ7o8VvPnxYLDKFe3I2m6HFYFWRhOGeDYxexIuaiF2iIAYFVUmnDuWpgnUiL4XJ3KHDsjkPzcV3z4D2Knr/El2VVXve8jhDjETfovmmN28+i2e29PXvKIymTskMFpFCQPc7wBY/Id7pmgb3SujKYNpkAS2sByDoRir0my49DDGfta0dENssJhFd3x+87fZbEj3cMiikg2pBwpTLgmfIUa5cVZU2s8JZ9wu7gaioYzvX+elHa3EHLcnEUoJTtSf9kjb+Nbq4ktMgYAO2wIC96t1LvmqK4Qn2cOdw5QNlRqALhqe5V31kyIcwRMK0AyIoOPhnSqtpYdFiR3LDTvZA8dU0vSsuchCwHNMeRUtKvdzN/tk+oeznyY/mpakUESN501lEKd/QFLtJZsDZTtNlcA8fU3kDtws4ZIMR0O5+PFmgQFSU8OMobf9ClUzy/wHTvYGyDuSwbOoPeS955QKkUKXCNMj33yrPr+ioHQ1BNwLX3VmMF4bNRBY/vr+CG0/EZi0Gwl0kyHGl0yWEtpQuu+/PaROJeOraWy5D1UoZZhY4n0zJZBt1eg3FZ2rhKv4gdUc50nZpeNWE8pIqZ6RQ7qPJuqfF1Z+G73iOSnLYCHDiiFmhD5ivf9IGkTAcWcBsQ/2wcSj9bFJr4DrKfsbQ4CkSWICWVn/W+InKkO6BTsBbYmvte5SvbaN+UOtiUSkHLBCCr8273VNgcB/hgtbUires3noxYZJxoczr+i7vdlEgQnWEKrpo0CifsFxGwYS3Yy2K79iwvDMaLPDf73zLSbuoUl6602F2Mzcjnals67f+gSpaDvWt7Kg9c/ZfGjq8oNxVaXJnX3gSDsO+fhwVAtnDApL+tL8cFfxGerW4KGi9/74woH+C3MMIViBtNnrpEuvxUW97Dg5nd40oGDeyi/q+8HdcxkneyFY=|JYdol19Yi+n1r7M+06EwK5JCi2s/CWqKui2Cy6hEb3k=".parse().unwrap(); + + let decrypted = device_key + .decrypt_user_key(protected_device_private_key, protected_user_key) + .unwrap(); + + assert_eq!(decrypted.key, user_key.key); + assert_eq!(decrypted.mac_key, user_key.mac_key); + } +} diff --git a/crates/bitwarden-crypto/src/keys/key_encryptable.rs b/crates/bitwarden-crypto/src/keys/key_encryptable.rs new file mode 100644 index 000000000..750647ee8 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/key_encryptable.rs @@ -0,0 +1,101 @@ +use std::{collections::HashMap, hash::Hash}; + +use rayon::prelude::*; + +use crate::error::Result; + +pub trait CryptoKey {} + +pub trait KeyEncryptable { + fn encrypt_with_key(self, key: &Key) -> Result; +} + +pub trait KeyDecryptable { + fn decrypt_with_key(&self, key: &Key) -> Result; +} + +impl, Key: CryptoKey, Output> KeyEncryptable> + for Option +{ + fn encrypt_with_key(self, key: &Key) -> Result> { + self.map(|e| e.encrypt_with_key(key)).transpose() + } +} + +impl, Key: CryptoKey, Output> KeyDecryptable> + for Option +{ + fn decrypt_with_key(&self, key: &Key) -> Result> { + self.as_ref().map(|e| e.decrypt_with_key(key)).transpose() + } +} + +impl, Key: CryptoKey, Output> KeyEncryptable + for Box +{ + fn encrypt_with_key(self, key: &Key) -> Result { + (*self).encrypt_with_key(key) + } +} + +impl, Key: CryptoKey, Output> KeyDecryptable + for Box +{ + fn decrypt_with_key(&self, key: &Key) -> Result { + (**self).decrypt_with_key(key) + } +} + +impl< + T: KeyEncryptable + Send + Sync, + Key: CryptoKey + Send + Sync, + Output: Send + Sync, + > KeyEncryptable> for Vec +{ + fn encrypt_with_key(self, key: &Key) -> Result> { + self.into_par_iter() + .map(|e| e.encrypt_with_key(key)) + .collect() + } +} + +impl< + T: KeyDecryptable + Send + Sync, + Key: CryptoKey + Send + Sync, + Output: Send + Sync, + > KeyDecryptable> for Vec +{ + fn decrypt_with_key(&self, key: &Key) -> Result> { + self.into_par_iter() + .map(|e| e.decrypt_with_key(key)) + .collect() + } +} + +impl< + T: KeyEncryptable + Send + Sync, + Key: CryptoKey + Send + Sync, + Output: Send + Sync, + Id: Hash + Eq + Send + Sync, + > KeyEncryptable> for HashMap +{ + fn encrypt_with_key(self, key: &Key) -> Result> { + self.into_par_iter() + .map(|(id, e)| Ok((id, e.encrypt_with_key(key)?))) + .collect() + } +} + +impl< + T: KeyDecryptable + Send + Sync, + Key: CryptoKey + Send + Sync, + Output: Send + Sync, + Id: Hash + Eq + Copy + Send + Sync, + > KeyDecryptable> for HashMap +{ + fn decrypt_with_key(&self, key: &Key) -> Result> { + self.into_par_iter() + .map(|(id, e)| Ok((*id, e.decrypt_with_key(key)?))) + .collect() + } +} diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs new file mode 100644 index 000000000..1fd678452 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -0,0 +1,265 @@ +use std::num::NonZeroU32; + +use base64::{engine::general_purpose::STANDARD, Engine}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::utils::{derive_kdf_key, stretch_kdf_key}; +use crate::{util, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum Kdf { + PBKDF2 { + iterations: NonZeroU32, + }, + Argon2id { + iterations: NonZeroU32, + memory: NonZeroU32, + parallelism: NonZeroU32, + }, +} + +impl Default for Kdf { + fn default() -> Self { + Kdf::PBKDF2 { + iterations: default_pbkdf2_iterations(), + } + } +} + +pub fn default_pbkdf2_iterations() -> NonZeroU32 { + NonZeroU32::new(600_000).expect("Non-zero number") +} +pub fn default_argon2_iterations() -> NonZeroU32 { + NonZeroU32::new(3).expect("Non-zero number") +} +pub fn default_argon2_memory() -> NonZeroU32 { + NonZeroU32::new(64).expect("Non-zero number") +} +pub fn default_argon2_parallelism() -> NonZeroU32 { + NonZeroU32::new(4).expect("Non-zero number") +} + +#[derive(Copy, Clone, JsonSchema)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum HashPurpose { + ServerAuthorization = 1, + LocalAuthorization = 2, +} + +/// Master Key. +/// +/// Derived from the users master password, used to protect the [UserKey]. +pub struct MasterKey(SymmetricCryptoKey); + +impl MasterKey { + pub fn new(key: SymmetricCryptoKey) -> MasterKey { + Self(key) + } + + /// 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) + } + + /// Derive the master key hash, used for local and remote password validation. + pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> Result { + let hash = util::pbkdf2(&self.0.key, password, purpose as u32); + + Ok(STANDARD.encode(hash)) + } + + /// Generate a new random user key and encrypt it with the master key. + pub fn make_user_key(&self) -> Result<(UserKey, EncString)> { + make_user_key(rand::thread_rng(), self) + } + + /// 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()) + } + + 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().expose(), + stretched_key + .mac_key + .as_ref() + .ok_or(CryptoError::InvalidMac)?, + &stretched_key.key, + ) + } +} + +/// Generate a new random user key and encrypt it with the master key. +fn make_user_key( + mut rng: impl rand::RngCore, + master_key: &MasterKey, +) -> Result<(UserKey, EncString)> { + let user_key = SymmetricCryptoKey::generate(&mut rng); + let protected = master_key.encrypt_user_key(&user_key)?; + Ok((UserKey::new(user_key), protected)) +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use rand::SeedableRng; + + use super::{make_user_key, HashPurpose, Kdf, MasterKey}; + use crate::{keys::symmetric_crypto_key::derive_symmetric_key, SymmetricCryptoKey}; + + #[test] + fn test_master_key_derive_pbkdf2() { + let master_key = MasterKey::derive( + &b"67t9b5g67$%Dh89n"[..], + "test_key".as_bytes(), + &Kdf::PBKDF2 { + iterations: NonZeroU32::new(10000).unwrap(), + }, + ) + .unwrap(); + + assert_eq!( + [ + 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167, + 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75 + ], + master_key.0.key.as_slice() + ); + assert_eq!(None, master_key.0.mac_key); + } + + #[test] + fn test_master_key_derive_argon2() { + let master_key = MasterKey::derive( + &b"67t9b5g67$%Dh89n"[..], + "test_key".as_bytes(), + &Kdf::Argon2id { + iterations: NonZeroU32::new(4).unwrap(), + memory: NonZeroU32::new(32).unwrap(), + parallelism: NonZeroU32::new(2).unwrap(), + }, + ) + .unwrap(); + + assert_eq!( + [ + 207, 240, 225, 177, 162, 19, 163, 76, 98, 106, 179, 175, 224, 9, 17, 240, 20, 147, + 237, 47, 246, 150, 141, 184, 62, 225, 131, 242, 51, 53, 225, 242 + ], + master_key.0.key.as_slice() + ); + assert_eq!(None, master_key.0.mac_key); + } + + #[test] + fn test_password_hash_pbkdf2() { + let password = "asdfasdf".as_bytes(); + let salt = "test_salt".as_bytes(); + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(100_000).unwrap(), + }; + + let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); + + assert_eq!( + "ZF6HjxUTSyBHsC+HXSOhZoXN+UuMnygV5YkWXCY4VmM=", + master_key + .derive_master_key_hash(password, HashPurpose::ServerAuthorization) + .unwrap(), + ); + } + + #[test] + fn test_password_hash_argon2id() { + let password = "asdfasdf".as_bytes(); + let salt = "test_salt".as_bytes(); + let kdf = Kdf::Argon2id { + iterations: NonZeroU32::new(4).unwrap(), + memory: NonZeroU32::new(32).unwrap(), + parallelism: NonZeroU32::new(2).unwrap(), + }; + + let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); + + assert_eq!( + "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=", + master_key + .derive_master_key_hash(password, HashPurpose::ServerAuthorization) + .unwrap(), + ); + } + + #[test] + fn test_make_user_key() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let master_key = MasterKey(SymmetricCryptoKey::new( + Box::pin( + [ + 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, + 167, 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75, + ] + .into(), + ), + None, + )); + + let (user_key, protected) = make_user_key(&mut rng, &master_key).unwrap(); + + assert_eq!( + user_key.0.key.as_slice(), + [ + 62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161, 44, 132, 14, + 195, 206, 154, 127, 59, 24, 27, 225, 136, 239, 113, 26, 30 + ] + ); + assert_eq!( + user_key.0.mac_key.as_ref().unwrap().as_slice(), + [ + 152, 76, 225, 114, 185, 33, 111, 65, 159, 68, 83, 103, 69, 109, 86, 25, 49, 74, 66, + 163, 218, 134, 176, 1, 56, 123, 253, 184, 14, 12, 254, 66 + ] + ); + + // Ensure we can decrypt the key and get back the same key + let decrypted = master_key.decrypt_user_key(protected).unwrap(); + + assert_eq!( + decrypted.key, user_key.0.key, + "Decrypted key doesn't match user key" + ); + assert_eq!( + decrypted.mac_key, user_key.0.mac_key, + "Decrypted key doesn't match user key" + ); + } + + #[test] + fn test_make_user_key2() { + let master_key = MasterKey(derive_symmetric_key("test1")); + + let user_key = derive_symmetric_key("test2"); + + let encrypted = master_key.encrypt_user_key(&user_key).unwrap(); + let decrypted = master_key.decrypt_user_key(encrypted).unwrap(); + + assert_eq!( + decrypted.key, user_key.key, + "Decrypted key doesn't match user key" + ); + assert_eq!( + decrypted.mac_key, user_key.mac_key, + "Decrypted key doesn't match user key" + ); + } +} diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs new file mode 100644 index 000000000..62b378808 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -0,0 +1,24 @@ +mod key_encryptable; +pub use key_encryptable::{CryptoKey, KeyDecryptable, KeyEncryptable}; +mod master_key; +pub use master_key::{ + default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, + default_pbkdf2_iterations, HashPurpose, Kdf, MasterKey, +}; +mod shareable_key; +pub use shareable_key::derive_shareable_key; +mod symmetric_crypto_key; +#[cfg(test)] +pub use symmetric_crypto_key::derive_symmetric_key; +pub use symmetric_crypto_key::SymmetricCryptoKey; +mod asymmetric_crypto_key; +pub use asymmetric_crypto_key::{ + AsymmetricCryptoKey, AsymmetricEncryptable, AsymmetricPublicCryptoKey, +}; +mod user_key; +pub use user_key::UserKey; +mod device_key; +pub use device_key::{DeviceKey, TrustDeviceResponse}; +mod pin_key; +pub use pin_key::PinKey; +mod utils; diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs new file mode 100644 index 000000000..475b7ffd9 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -0,0 +1,39 @@ +use crate::{ + keys::{ + key_encryptable::CryptoKey, + utils::{derive_kdf_key, stretch_kdf_key}, + }, + EncString, Kdf, KeyEncryptable, Result, SymmetricCryptoKey, +}; + +/// Pin Key. +/// +/// Derived from a specific password, used for pin encryption and exports. +pub struct PinKey(SymmetricCryptoKey); + +impl PinKey { + pub fn new(key: SymmetricCryptoKey) -> Self { + Self(key) + } + + /// 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) + } +} + +impl CryptoKey for PinKey {} + +impl KeyEncryptable for &[u8] { + fn encrypt_with_key(self, key: &PinKey) -> Result { + let stretched_key = stretch_kdf_key(&key.0)?; + + self.encrypt_with_key(&stretched_key) + } +} + +impl KeyEncryptable for String { + fn encrypt_with_key(self, key: &PinKey) -> Result { + self.as_bytes().encrypt_with_key(key) + } +} diff --git a/crates/bitwarden-crypto/src/keys/shareable_key.rs b/crates/bitwarden-crypto/src/keys/shareable_key.rs new file mode 100644 index 000000000..e84e05ca3 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/shareable_key.rs @@ -0,0 +1,49 @@ +use std::pin::Pin; + +use aes::cipher::typenum::U64; +use generic_array::GenericArray; +use hmac::Mac; + +use crate::{ + keys::SymmetricCryptoKey, + util::{hkdf_expand, PbkdfSha256Hmac}, +}; + +/// Derive a shareable key using hkdf from secret and name. +/// +/// A specialized variant of this function was called `CryptoService.makeSendKey` in the Bitwarden +/// `clients` repository. +pub fn derive_shareable_key( + secret: [u8; 16], + name: &str, + info: Option<&str>, +) -> SymmetricCryptoKey { + // Because all inputs are fixed size, we can unwrap all errors here without issue + + // TODO: Are these the final `key` and `info` parameters or should we change them? I followed + // the pattern used for sends + let 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 mut key: Pin>> = + hkdf_expand(&res, info).expect("Input is a valid size"); + + SymmetricCryptoKey::try_from(key.as_mut_slice()).expect("Key is a valid size") +} + +#[cfg(test)] +mod tests { + use super::derive_shareable_key; + + #[test] + fn test_derive_shareable_key() { + let key = derive_shareable_key(*b"&/$%F1a895g67HlX", "test_key", None); + assert_eq!(key.to_base64().expose(), "4PV6+PcmF2w7YHRatvyMcVQtI7zvCyssv/wFWmzjiH6Iv9altjmDkuBD1aagLVaLezbthbSe+ktR+U6qswxNnQ=="); + + let key = derive_shareable_key(*b"67t9b5g67$%Dh89n", "test_key", Some("test")); + assert_eq!(key.to_base64().expose(), "F9jVQmrACGx9VUPjuzfMYDjr726JtL300Y3Yg+VYUnVQtQ1s8oImJ5xtp1KALC9h2nav04++1LDW4iFD+infng=="); + } +} diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs new file mode 100644 index 000000000..23d09cbca --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -0,0 +1,159 @@ +use std::pin::Pin; + +use aes::cipher::typenum::U32; +use base64::engine::general_purpose::STANDARD; +use generic_array::GenericArray; +use rand::Rng; +use zeroize::Zeroize; + +use super::key_encryptable::CryptoKey; +use crate::{CryptoError, SensitiveString, SensitiveVec}; + +/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString) +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, + // we use a Box to keep the values on the heap. We also pin the box to make sure + // that the contents can't be pulled out of the box and moved + pub(crate) key: Pin>>, + pub(crate) mac_key: Option>>>, +} + +impl Drop for SymmetricCryptoKey { + fn drop(&mut self) { + self.key.zeroize(); + if let Some(mac_key) = &mut self.mac_key { + mac_key.zeroize(); + } + } +} + +impl zeroize::ZeroizeOnDrop for SymmetricCryptoKey {} + +impl SymmetricCryptoKey { + const KEY_LEN: usize = 32; + const MAC_LEN: usize = 32; + + /// Generate a new random [SymmetricCryptoKey] + pub fn generate(mut rng: impl rand::RngCore) -> Self { + let mut key = Box::pin(GenericArray::::default()); + let mut mac_key = Box::pin(GenericArray::::default()); + + rng.fill(key.as_mut_slice()); + rng.fill(mac_key.as_mut_slice()); + + SymmetricCryptoKey { + key, + mac_key: Some(mac_key), + } + } + + pub(crate) fn new( + key: Pin>>, + mac_key: Option>>>, + ) -> Self { + Self { key, mac_key } + } + + fn total_len(&self) -> usize { + self.key.len() + self.mac_key.as_ref().map_or(0, |mac| mac.len()) + } + + pub fn to_base64(&self) -> SensitiveString { + self.to_vec().encode_base64(STANDARD) + } + + pub fn to_vec(&self) -> SensitiveVec { + let mut buf = SensitiveVec::new(Box::new(Vec::with_capacity(self.total_len()))); + + buf.expose_mut().extend_from_slice(&self.key); + if let Some(mac) = &self.mac_key { + buf.expose_mut().extend_from_slice(mac); + } + buf + } +} + +impl TryFrom for SymmetricCryptoKey { + type Error = CryptoError; + + fn try_from(value: SensitiveString) -> Result { + SymmetricCryptoKey::try_from(value.decode_base64(STANDARD)?) + } +} + +impl TryFrom for SymmetricCryptoKey { + type Error = CryptoError; + + fn try_from(mut value: SensitiveVec) -> Result { + let val = value.expose_mut(); + SymmetricCryptoKey::try_from(val.as_mut_slice()) + } +} + +impl TryFrom<&mut [u8]> for SymmetricCryptoKey { + type Error = CryptoError; + + /// Note: This function takes the byte slice by mutable reference and will zero out all + /// the data in it. This is to prevent the key from being left in memory. + fn try_from(value: &mut [u8]) -> Result { + let result = if value.len() == Self::KEY_LEN + Self::MAC_LEN { + let mut key = Box::pin(GenericArray::::default()); + let mut mac_key = Box::pin(GenericArray::::default()); + + key.copy_from_slice(&value[..Self::KEY_LEN]); + mac_key.copy_from_slice(&value[Self::KEY_LEN..]); + + Ok(SymmetricCryptoKey { + key, + mac_key: Some(mac_key), + }) + } else if value.len() == Self::KEY_LEN { + let mut key = Box::pin(GenericArray::::default()); + + key.copy_from_slice(&value[..Self::KEY_LEN]); + + Ok(SymmetricCryptoKey { key, mac_key: None }) + } else { + Err(CryptoError::InvalidKeyLen) + }; + + value.zeroize(); + result + } +} + +impl CryptoKey for SymmetricCryptoKey {} + +// We manually implement these to make sure we don't print any sensitive data +impl std::fmt::Debug for SymmetricCryptoKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SymmetricCryptoKey").finish() + } +} + +#[cfg(test)] +pub fn derive_symmetric_key(name: &str) -> SymmetricCryptoKey { + use crate::{derive_shareable_key, generate_random_bytes}; + + let secret: [u8; 16] = generate_random_bytes(); + derive_shareable_key(secret, name, None) +} + +#[cfg(test)] +mod tests { + use super::{derive_symmetric_key, SymmetricCryptoKey}; + use crate::SensitiveString; + + #[test] + fn test_symmetric_crypto_key() { + let key = derive_symmetric_key("test"); + let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap(); + assert_eq!(key.key, key2.key); + assert_eq!(key.mac_key, key2.mac_key); + + let key = SensitiveString::test("UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ=="); + let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap(); + assert_eq!(key, key2.to_base64()); + } +} diff --git a/crates/bitwarden-crypto/src/keys/user_key.rs b/crates/bitwarden-crypto/src/keys/user_key.rs new file mode 100644 index 000000000..6620236ec --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/user_key.rs @@ -0,0 +1,19 @@ +use crate::{ + rsa::{make_key_pair, RsaKeyPair}, + Result, SymmetricCryptoKey, +}; + +/// User Key +/// +/// The User Key is the symmetric encryption key used to decrypt the user's vault. +pub struct UserKey(pub SymmetricCryptoKey); + +impl UserKey { + pub fn new(key: SymmetricCryptoKey) -> Self { + Self(key) + } + + pub fn make_key_pair(&self) -> Result { + make_key_pair(&self.0) + } +} diff --git a/crates/bitwarden-crypto/src/keys/utils.rs b/crates/bitwarden-crypto/src/keys/utils.rs new file mode 100644 index 000000000..a2df336e1 --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/utils.rs @@ -0,0 +1,82 @@ +use std::pin::Pin; + +use generic_array::{typenum::U32, GenericArray}; +use sha2::Digest; + +use crate::{util::hkdf_expand, Kdf, Result, SymmetricCryptoKey}; + +/// 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::Argon2id { + iterations, + memory, + parallelism, + } => { + 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 salt_sha = sha2::Sha256::new().chain_update(salt).finalize(); + + let mut hash = [0u8; 32]; + argon.hash_password_into(secret, &salt_sha, &mut hash)?; + hash + } + }; + SymmetricCryptoKey::try_from(hash.as_mut_slice()) +} + +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"))?; + + Ok(SymmetricCryptoKey::new(key, Some(mac_key))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stretch_kdf_key() { + let key = SymmetricCryptoKey::new( + Box::pin( + [ + 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, + 167, 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75, + ] + .into(), + ), + None, + ); + + let stretched = stretch_kdf_key(&key).unwrap(); + + assert_eq!( + [ + 111, 31, 178, 45, 238, 152, 37, 114, 143, 215, 124, 83, 135, 173, 195, 23, 142, + 134, 120, 249, 61, 132, 163, 182, 113, 197, 189, 204, 188, 21, 237, 96 + ], + stretched.key.as_slice() + ); + assert_eq!( + [ + 221, 127, 206, 234, 101, 27, 202, 38, 86, 52, 34, 28, 78, 28, 185, 16, 48, 61, 127, + 166, 209, 247, 194, 87, 232, 26, 48, 85, 193, 249, 179, 155 + ], + stretched.mac_key.as_ref().unwrap().as_slice() + ); + } +} diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs new file mode 100644 index 000000000..8c368580a --- /dev/null +++ b/crates/bitwarden-crypto/src/lib.rs @@ -0,0 +1,50 @@ +//! # Bitwarden Cryptographic primitives +//! +//! This crate contains the cryptographic primitives used throughout the SDK. The crate makes a +//! best effort to abstract away cryptographic concepts into concepts such as [`EncString`], +//! [`AsymmetricEncString`] and [`SymmetricCryptoKey`]. +//! +//! ## Conventions: +//! +//! - Pure Functions that deterministically "derive" keys from input are prefixed with `derive_`. +//! - Functions that generate non deterministically keys are prefixed with `make_`. +//! +//! ## Differences from `clients` +//! +//! There are some noteworthy differences compared to the other Bitwarden +//! [clients](https://github.com/bitwarden/clients). These changes are made in an effort to +//! introduce conventions in how we name things, improve best practices and abstracting away +//! internal complexity. +//! +//! - `CryptoService.makeSendKey` & `AccessService.createAccessToken` are replaced by the generic +//! `derive_shareable_key` +//! - MasterKey operations such as `makeMasterKey` and `hashMasterKey` are moved to the MasterKey +//! struct. + +mod aes; +mod enc_string; +pub use enc_string::{AsymmetricEncString, EncString}; +mod encryptable; +pub use encryptable::{Decryptable, Encryptable, KeyContainer, LocateKey}; +mod error; +pub use error::CryptoError; +pub(crate) use error::Result; +mod fingerprint; +pub use fingerprint::fingerprint; +mod keys; +pub use keys::*; +mod rsa; +pub use crate::rsa::RsaKeyPair; +mod util; +pub use util::generate_random_bytes; +mod wordlist; +pub use util::pbkdf2; +pub use wordlist::EFF_LONG_WORD_LIST; +mod sensitive; +pub use sensitive::*; + +#[cfg(feature = "mobile")] +uniffi::setup_scaffolding!(); + +#[cfg(feature = "mobile")] +mod uniffi_support; diff --git a/crates/bitwarden-crypto/src/rsa.rs b/crates/bitwarden-crypto/src/rsa.rs new file mode 100644 index 000000000..98f1282cc --- /dev/null +++ b/crates/bitwarden-crypto/src/rsa.rs @@ -0,0 +1,58 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use rsa::{ + pkcs8::{EncodePrivateKey, EncodePublicKey}, + Oaep, RsaPrivateKey, RsaPublicKey, +}; +use sha1::Sha1; + +use crate::{ + error::{Result, RsaError}, + CryptoError, EncString, SymmetricCryptoKey, +}; + +/// RSA Key Pair +/// +/// Consists of a public key and an encrypted private key. +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct RsaKeyPair { + /// Base64 encoded DER representation of the public key + pub public: String, + /// Encrypted PKCS8 private key + pub private: EncString, +} + +pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { + let mut rng = rand::thread_rng(); + let bits = 2048; + let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); + let pub_key = RsaPublicKey::from(&priv_key); + + let spki = pub_key + .to_public_key_der() + .map_err(|_| RsaError::CreatePublicKey)?; + + let b64 = STANDARD.encode(spki.as_bytes()); + let pkcs = priv_key + .to_pkcs8_der() + .map_err(|_| RsaError::CreatePrivateKey)?; + + let protected = EncString::encrypt_aes256_hmac( + pkcs.as_bytes(), + key.mac_key.as_ref().ok_or(CryptoError::InvalidMac)?, + &key.key, + )?; + + Ok(RsaKeyPair { + public: b64, + private: protected, + }) +} + +pub(super) fn encrypt_rsa2048_oaep_sha1(public_key: &RsaPublicKey, data: &[u8]) -> Result> { + let mut rng = rand::thread_rng(); + + let padding = Oaep::new::(); + public_key + .encrypt(&mut rng, padding, data) + .map_err(|e| CryptoError::RsaError(e.into())) +} diff --git a/crates/bitwarden-crypto/src/sensitive/decrypted.rs b/crates/bitwarden-crypto/src/sensitive/decrypted.rs new file mode 100644 index 000000000..02096d081 --- /dev/null +++ b/crates/bitwarden-crypto/src/sensitive/decrypted.rs @@ -0,0 +1,16 @@ +use zeroize::Zeroize; + +use crate::{CryptoError, CryptoKey, KeyEncryptable, Sensitive}; + +/// Type alias for a [`Sensitive`] value to denote decrypted data. +pub type Decrypted = Sensitive; +pub type DecryptedVec = Decrypted>; +pub type DecryptedString = Decrypted; + +impl + Zeroize + Clone, Key: CryptoKey, Output> + KeyEncryptable for Decrypted +{ + fn encrypt_with_key(self, key: &Key) -> Result { + self.value.clone().encrypt_with_key(key) + } +} diff --git a/crates/bitwarden-crypto/src/sensitive/mod.rs b/crates/bitwarden-crypto/src/sensitive/mod.rs new file mode 100644 index 000000000..0da2329b9 --- /dev/null +++ b/crates/bitwarden-crypto/src/sensitive/mod.rs @@ -0,0 +1,5 @@ +#[allow(clippy::module_inception)] +mod sensitive; +pub use sensitive::{Sensitive, SensitiveString, SensitiveVec}; +mod decrypted; +pub use decrypted::{Decrypted, DecryptedString, DecryptedVec}; diff --git a/crates/bitwarden-crypto/src/sensitive/sensitive.rs b/crates/bitwarden-crypto/src/sensitive/sensitive.rs new file mode 100644 index 000000000..996b20330 --- /dev/null +++ b/crates/bitwarden-crypto/src/sensitive/sensitive.rs @@ -0,0 +1,220 @@ +use std::{ + borrow::Cow, + fmt::{self, Formatter}, +}; + +use schemars::JsonSchema; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::CryptoError; + +/// Wrapper for sensitive values which makes a best effort to enforce zeroization of the inner value +/// on drop. The inner value exposes a [`Sensitive::expose`] method which returns a reference to the +/// inner value. Care must be taken to avoid accidentally exposing the inner value through copying +/// or cloning. +/// +/// Internally [`Sensitive`] contains a [`Box`] which ensures the value is placed on the heap. It +/// implements the [`Drop`] trait which calls `zeroize` on the inner value. +#[derive(PartialEq, Clone, Zeroize, ZeroizeOnDrop)] +pub struct Sensitive { + pub(super) value: Box, +} + +/// Important: This type does not protect against reallocations made by the Vec. +/// This means that if you insert any elements past the capacity, the data will be copied to a +/// new allocation and the old allocation will not be zeroized. +/// To avoid this, use Vec::with_capacity to preallocate the capacity you need. +pub type SensitiveVec = Sensitive>; + +/// Important: This type does not protect against reallocations made by the String. +/// This means that if you insert any characters past the capacity, the data will be copied to a +/// new allocation and the old allocation will not be zeroized. +/// To avoid this, use String::with_capacity to preallocate the capacity you need. +pub type SensitiveString = Sensitive; + +impl Sensitive { + /// Create a new [`Sensitive`] value. In an attempt to avoid accidentally placing this on the + /// stack it only accepts a [`Box`] value. The rust compiler should be able to optimize away the + /// initial stack allocation presuming the value is not used before being boxed. + pub fn new(value: Box) -> Self { + Self { value } + } + + /// Expose the inner value. By exposing the inner value, you take responsibility for ensuring + /// that any copy of the value is zeroized. + pub fn expose(&self) -> &V { + &self.value + } + + /// Expose the inner value mutable. By exposing the inner value, you take responsibility for + /// ensuring that any copy of the value is zeroized. + pub fn expose_mut(&mut self) -> &mut V { + &mut self.value + } +} + +/// Helper to convert a `Sensitive>` to a `Sensitive`, care is taken to ensure any +/// intermediate copies are zeroed to avoid leaking sensitive data. +impl TryFrom for SensitiveString { + type Error = CryptoError; + + fn try_from(mut v: SensitiveVec) -> Result { + let value = std::mem::take(&mut v.value); + + let rtn = String::from_utf8(*value).map_err(|_| CryptoError::InvalidUtf8String); + rtn.map(|v| Sensitive::new(Box::new(v))) + } +} + +impl SensitiveString { + pub fn decode_base64(self, engine: T) -> Result { + // Prevent accidental copies by allocating the full size + let len = base64::decoded_len_estimate(self.value.len()); + let mut value = SensitiveVec::new(Box::new(Vec::with_capacity(len))); + + engine + .decode_vec(self.value.as_ref(), &mut value.value) + .map_err(|_| CryptoError::InvalidKey)?; + + Ok(value) + } +} + +impl SensitiveVec { + pub fn encode_base64(self, engine: T) -> SensitiveString { + use base64::engine::Config; + + // Prevent accidental copies by allocating the full size + let padding = engine.config().encode_padding(); + let len = base64::encoded_len(self.value.len(), padding).expect("Valid length"); + let mut value = SensitiveString::new(Box::new(String::with_capacity(len))); + + engine.encode_string(self.value.as_ref(), &mut value.value); + + value + } +} + +impl Default for Sensitive { + fn default() -> Self { + Self::new(Box::default()) + } +} + +impl fmt::Debug for Sensitive { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Sensitive") + .field("type", &std::any::type_name::()) + .field("value", &"********") + .finish() + } +} + +/// Unfortunately once we serialize a `SensitiveString` we can't control the future memory. +impl Serialize for Sensitive { + fn serialize(&self, serializer: S) -> Result { + self.value.serialize(serializer) + } +} + +impl<'de, V: Zeroize + Deserialize<'de>> Deserialize<'de> for Sensitive { + fn deserialize>(deserializer: D) -> Result { + Ok(Self::new(Box::new(V::deserialize(deserializer)?))) + } +} + +/// Transparently expose the inner value for serialization +impl JsonSchema for Sensitive { + fn schema_name() -> String { + V::schema_name() + } + + fn schema_id() -> Cow<'static, str> { + V::schema_id() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + V::json_schema(gen) + } +} + +impl Sensitive { + // We use a lot of `&str` in our tests, so we expose this helper + // to make it easier. + // IMPORTANT: This should not be used outside of test code + // Note that we can't just mark it with #[cfg(test)] because that only applies + // when testing this crate, not when testing other crates that depend on it. + // By at least limiting it to &'static str we should be able to avoid accidental usages + pub fn test(value: &'static str) -> Self { + Self::new(Box::new(value.to_string())) + } +} + +#[cfg(test)] +mod tests { + use schemars::schema_for; + + use super::*; + + #[test] + fn test_debug() { + let string = Sensitive::test("test"); + assert_eq!( + format!("{:?}", string), + "Sensitive { type: \"alloc::string::String\", value: \"********\" }" + ); + + let vector = Sensitive::new(Box::new(vec![1, 2, 3])); + assert_eq!( + format!("{:?}", vector), + "Sensitive { type: \"alloc::vec::Vec\", value: \"********\" }" + ); + } + + #[test] + fn test_schemars() { + #[derive(JsonSchema)] + struct TestStruct { + #[allow(dead_code)] + s: SensitiveString, + #[allow(dead_code)] + v: SensitiveVec, + } + + let schema = schema_for!(TestStruct); + let json = serde_json::to_string_pretty(&schema).unwrap(); + let expected = r##"{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TestStruct", + "type": "object", + "required": ["s", "v"], + "properties": { + "s": { + "$ref": "#/definitions/String" + }, + "v": { + "$ref": "#/definitions/Array_of_uint8" + } + }, + "definitions": { + "Array_of_uint8": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "String": { + "type": "string" + } + } + }"##; + + assert_eq!( + json.parse::().unwrap(), + expected.parse::().unwrap() + ); + } +} diff --git a/crates/bitwarden-crypto/src/uniffi_support.rs b/crates/bitwarden-crypto/src/uniffi_support.rs new file mode 100644 index 000000000..f99b55da5 --- /dev/null +++ b/crates/bitwarden-crypto/src/uniffi_support.rs @@ -0,0 +1,68 @@ +use std::{num::NonZeroU32, str::FromStr}; + +use crate::{ + AsymmetricEncString, CryptoError, EncString, SensitiveString, UniffiCustomTypeConverter, +}; + +uniffi::custom_type!(NonZeroU32, u32); + +impl UniffiCustomTypeConverter for NonZeroU32 { + type Builtin = u32; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Self::new(val).ok_or(CryptoError::ZeroNumber.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.get() + } +} + +uniffi::custom_type!(EncString, String); + +impl UniffiCustomTypeConverter for EncString { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + val.parse().map_err(|e: CryptoError| e.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +uniffi::custom_type!(AsymmetricEncString, String); + +impl UniffiCustomTypeConverter for AsymmetricEncString { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Self::from_str(&val).map_err(|e| e.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +uniffi::custom_type!(SensitiveString, String); + +impl UniffiCustomTypeConverter for SensitiveString { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(SensitiveString::new(Box::new(val))) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.expose().to_owned() + } +} + +/// Uniffi doesn't seem to be generating the SensitiveString unless it's being used by +/// a record somewhere. This is a workaround to make sure the type is generated. +#[derive(uniffi::Record)] +struct SupportSensitiveString { + sensitive_string: SensitiveString, +} diff --git a/crates/bitwarden-crypto/src/util.rs b/crates/bitwarden-crypto/src/util.rs new file mode 100644 index 000000000..ba60db366 --- /dev/null +++ b/crates/bitwarden-crypto/src/util.rs @@ -0,0 +1,70 @@ +use std::pin::Pin; + +use ::aes::cipher::{ArrayLength, Unsigned}; +use generic_array::GenericArray; +use hmac::digest::OutputSizeUser; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; + +use crate::{CryptoError, Result}; + +pub(crate) type PbkdfSha256Hmac = hmac::Hmac; +pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize = + <::OutputSize as Unsigned>::USIZE; + +/// [RFC5869](https://datatracker.ietf.org/doc/html/rfc5869) HKDF-Expand operation +pub(crate) fn hkdf_expand>( + prk: &[u8], + info: Option<&str>, +) -> Result>>> { + let hkdf = hkdf::Hkdf::::from_prk(prk).map_err(|_| CryptoError::InvalidKeyLen)?; + let mut key = Box::>::default(); + + let i = info.map(|i| i.as_bytes()).unwrap_or(&[]); + hkdf.expand(i, &mut key) + .map_err(|_| CryptoError::InvalidKeyLen)?; + + Ok(Box::into_pin(key)) +} + +/// Generate random bytes that are cryptographically secure +pub fn generate_random_bytes() -> T +where + Standard: Distribution, +{ + rand::thread_rng().gen() +} + +pub fn pbkdf2(password: &[u8], salt: &[u8], rounds: u32) -> [u8; PBKDF_SHA256_HMAC_OUT_SIZE] { + pbkdf2::pbkdf2_array::(password, salt, rounds) + .expect("hash is a valid fixed size") +} + +#[cfg(test)] +mod tests { + use aes::cipher::typenum::U64; + + use super::*; + + #[test] + fn test_hkdf_expand() { + let prk = &[ + 23, 152, 120, 41, 214, 16, 156, 133, 71, 226, 178, 135, 208, 255, 66, 101, 189, 70, + 173, 30, 39, 215, 175, 236, 38, 180, 180, 62, 196, 4, 159, 70, + ]; + let info = Some("info"); + + let result: Pin>> = hkdf_expand(prk, info).unwrap(); + + let expected_output: [u8; 64] = [ + 6, 114, 42, 38, 87, 231, 30, 109, 30, 255, 104, 129, 255, 94, 92, 108, 124, 145, 215, + 208, 17, 60, 135, 22, 70, 158, 40, 53, 45, 182, 8, 63, 65, 87, 239, 234, 185, 227, 153, + 122, 115, 205, 144, 56, 102, 149, 92, 139, 217, 102, 119, 57, 37, 57, 251, 178, 18, 52, + 94, 77, 132, 215, 239, 100, + ]; + + assert_eq!(result.as_slice(), expected_output); + } +} diff --git a/crates/bitwarden/src/wordlist.rs b/crates/bitwarden-crypto/src/wordlist.rs similarity index 99% rename from crates/bitwarden/src/wordlist.rs rename to crates/bitwarden-crypto/src/wordlist.rs index 4974969e6..4cc30ff74 100644 --- a/crates/bitwarden/src/wordlist.rs +++ b/crates/bitwarden-crypto/src/wordlist.rs @@ -1,6 +1,5 @@ // EFF's Long Wordlist from https://www.eff.org/dice -#[cfg(feature = "internal")] -pub(crate) const EFF_LONG_WORD_LIST: &[&str] = &[ +pub const EFF_LONG_WORD_LIST: &[&str] = &[ "abacus", "abdomen", "abdominal", diff --git a/crates/bitwarden-crypto/uniffi.toml b/crates/bitwarden-crypto/uniffi.toml new file mode 100644 index 000000000..3cac32ecc --- /dev/null +++ b/crates/bitwarden-crypto/uniffi.toml @@ -0,0 +1,9 @@ +[bindings.kotlin] +package_name = "com.bitwarden.crypto" +generate_immutable_records = true +android = true + +[bindings.swift] +ffi_module_name = "BitwardenCryptoFFI" +module_name = "BitwardenCrypto" +generate_immutable_records = true diff --git a/crates/bitwarden-exporters/Cargo.toml b/crates/bitwarden-exporters/Cargo.toml new file mode 100644 index 000000000..fc118b922 --- /dev/null +++ b/crates/bitwarden-exporters/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bitwarden-exporters" +description = """ +Internal crate for the bitwarden crate. Do not use. +""" +exclude = ["/resources"] + +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] +base64 = ">=0.21.2, <0.22" +bitwarden-crypto = { workspace = true } +chrono = { version = ">=0.4.26, <0.5", features = [ + "clock", + "serde", + "std", +], default-features = false } +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"] } + +[lints] +workspace = true diff --git a/crates/bitwarden-exporters/README.md b/crates/bitwarden-exporters/README.md new file mode 100644 index 000000000..59936680f --- /dev/null +++ b/crates/bitwarden-exporters/README.md @@ -0,0 +1,6 @@ +# Bitwarden Exporters + +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-exporters/resources/json_export.json b/crates/bitwarden-exporters/resources/json_export.json new file mode 100644 index 000000000..ad5380550 --- /dev/null +++ b/crates/bitwarden-exporters/resources/json_export.json @@ -0,0 +1,146 @@ +{ + "encrypted": false, + "folders": [ + { + "id": "942e2984-1b9a-453b-b039-b107012713b9", + "name": "Important" + } + ], + "items": [ + { + "id": "25c8c414-b446-48e9-a1bd-b10700bbd740", + "folderId": "942e2984-1b9a-453b-b039-b107012713b9", + "organizationId": null, + "collectionIds": null, + "name": "Bitwarden", + "notes": "My note", + "type": 1, + "login": { + "username": "test@bitwarden.com", + "password": "asdfasdfasdf", + "uris": [ + { + "uri": "https://vault.bitwarden.com", + "match": null + } + ], + "totp": "ABC", + "fido2Credentials": [] + }, + "favorite": true, + "reprompt": 0, + "fields": [ + { + "name": "Text", + "value": "A", + "type": 0, + "linkedId": null + }, + { + "name": "Hidden", + "value": "B", + "type": 1, + "linkedId": null + }, + { + "name": "Boolean (true)", + "value": "true", + "type": 2, + "linkedId": null + }, + { + "name": "Boolean (false)", + "value": "false", + "type": 2, + "linkedId": null + }, + { + "name": "Linked", + "value": null, + "type": 3, + "linkedId": 101 + } + ], + "passwordHistory": null, + "revisionDate": "2024-01-30T14:09:33.753Z", + "creationDate": "2024-01-30T11:23:54.416Z", + "deletedDate": null + }, + { + "id": "23f0f877-42b1-4820-a850-b10700bc41eb", + "folderId": null, + "organizationId": null, + "collectionIds": null, + "name": "My secure note", + "notes": "Very secure!", + "type": 2, + "secureNote": { + "type": 0 + }, + "favorite": false, + "reprompt": 0, + "passwordHistory": null, + "revisionDate": "2024-01-30T11:25:25.466Z", + "creationDate": "2024-01-30T11:25:25.466Z", + "deletedDate": null + }, + { + "id": "3ed8de45-48ee-4e26-a2dc-b10701276c53", + "folderId": null, + "organizationId": null, + "collectionIds": null, + "name": "My card", + "notes": null, + "type": 3, + "card": { + "cardholderName": "John Doe", + "expMonth": "1", + "expYear": "2032", + "code": "123", + "brand": "Visa", + "number": "4111111111111111" + }, + "favorite": false, + "reprompt": 0, + "passwordHistory": null, + "revisionDate": "2024-01-30T17:55:36.150Z", + "creationDate": "2024-01-30T17:55:36.150Z", + "deletedDate": null + }, + { + "id": "41cc3bc1-c3d9-4637-876c-b10701273712", + "folderId": "942e2984-1b9a-453b-b039-b107012713b9", + "organizationId": null, + "collectionIds": null, + "name": "My identity", + "notes": null, + "type": 4, + "identity": { + "title": "Mr", + "firstName": "John", + "middleName": null, + "lastName": "Doe", + "address1": null, + "address2": null, + "address3": null, + "city": null, + "state": null, + "postalCode": null, + "country": null, + "company": "Bitwarden", + "email": null, + "phone": null, + "ssn": null, + "username": "JDoe", + "passportNumber": null, + "licenseNumber": null + }, + "favorite": false, + "reprompt": 0, + "passwordHistory": null, + "revisionDate": "2024-01-30T17:54:50.706Z", + "creationDate": "2024-01-30T17:54:50.706Z", + "deletedDate": null + } + ] +} \ No newline at end of file diff --git a/crates/bitwarden-exporters/src/csv.rs b/crates/bitwarden-exporters/src/csv.rs new file mode 100644 index 000000000..eb60fca21 --- /dev/null +++ b/crates/bitwarden-exporters/src/csv.rs @@ -0,0 +1,266 @@ +use std::collections::HashMap; + +use csv::Writer; +use serde::Serializer; +use thiserror::Error; +use uuid::Uuid; + +use crate::{Cipher, CipherType, Field, Folder}; + +#[derive(Debug, Error)] +pub enum CsvError { + #[error("CSV error")] + Csv, +} + +pub(crate) fn export_csv(folders: Vec, ciphers: Vec) -> Result { + let folders: HashMap = folders.into_iter().map(|f| (f.id, f.name)).collect(); + + let rows = ciphers + .into_iter() + .filter(|c| matches!(c.r#type, CipherType::Login(_) | CipherType::SecureNote(_))) + .map(|c| { + let login = if let CipherType::Login(l) = &c.r#type { + Some(l) + } else { + None + }; + + CsvRow { + folder: c + .folder_id + .and_then(|f| folders.get(&f)) + .map(|f| f.to_owned()), + favorite: c.favorite, + r#type: c.r#type.to_string(), + name: c.name.to_owned(), + notes: c.notes.to_owned(), + fields: c.fields, + reprompt: c.reprompt, + login_uri: login + .map(|l| l.login_uris.iter().flat_map(|l| l.uri.clone()).collect()) + .unwrap_or_default(), + login_username: login.and_then(|l| l.username.clone()), + login_password: login.and_then(|l| l.password.clone()), + login_totp: login.and_then(|l| l.totp.clone()), + } + }); + + let mut wtr = Writer::from_writer(vec![]); + for row in rows { + wtr.serialize(row).expect("Serialize should be infallible"); + } + + String::from_utf8(wtr.into_inner().map_err(|_| CsvError::Csv)?).map_err(|_| CsvError::Csv) +} + +/// CSV export format. See https://bitwarden.com/help/condition-bitwarden-import/#condition-a-csv +/// +/// Be careful when changing this struct to maintain compatibility with old exports. +#[derive(serde::Serialize)] +struct CsvRow { + folder: Option, + #[serde(serialize_with = "bool_serialize")] + favorite: bool, + r#type: String, + name: String, + notes: Option, + #[serde(serialize_with = "fields_serialize")] + fields: Vec, + reprompt: u8, + #[serde(serialize_with = "vec_serialize")] + login_uri: Vec, + login_username: Option, + login_password: Option, + login_totp: Option, +} + +fn vec_serialize(x: &[String], s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(x.join(",").as_str()) +} + +fn bool_serialize(x: &bool, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(if *x { "1" } else { "" }) +} + +fn fields_serialize(x: &[Field], s: S) -> Result +where + S: Serializer, +{ + s.serialize_str( + x.iter() + .map(|f| { + format!( + "{}: {}", + f.name.to_owned().unwrap_or_default(), + f.value.to_owned().unwrap_or_default() + ) + }) + .collect::>() + .join("\n") + .as_str(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Card, Identity, Login, LoginUri}; + + #[test] + fn test_export_csv() { + let folders = vec![ + Folder { + id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), + name: "Test Folder A".to_string(), + }, + Folder { + id: "583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap(), + name: "Test Folder B".to_string(), + }, + ]; + let ciphers = vec![ + Cipher { + id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), + folder_id: None, + name: "test@bitwarden.com".to_string(), + notes: None, + r#type: CipherType::Login(Box::new(Login { + username: Some("test@bitwarden.com".to_string()), + password: Some("Abc123".to_string()), + login_uris: vec![LoginUri { + uri: Some("https://google.com".to_string()), + r#match: None, + }], + totp: None, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + deleted_date: None, + }, + Cipher { + id: "7dd81bd0-cc72-4f42-96e7-b0fc014e71a3".parse().unwrap(), + folder_id: Some("583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap()), + name: "Steam Account".to_string(), + notes: None, + r#type: CipherType::Login(Box::new(Login { + username: Some("steam".to_string()), + password: Some("3Pvb8u7EfbV*nJ".to_string()), + login_uris: vec![LoginUri { + uri: Some("https://steampowered.com".to_string()), + r#match: None, + }], + totp: Some("steam://ABCD123".to_string()), + })), + favorite: true, + reprompt: 0, + fields: vec![ + Field { + name: Some("Test".to_string()), + value: Some("v".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Hidden".to_string()), + value: Some("asdfer".to_string()), + r#type: 1, + linked_id: None, + }, + ], + revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + deleted_date: None, + }, + ]; + + let csv = export_csv(folders, ciphers).unwrap(); + let expected = [ + "folder,favorite,type,name,notes,fields,reprompt,login_uri,login_username,login_password,login_totp", + ",,login,test@bitwarden.com,,,0,https://google.com,test@bitwarden.com,Abc123,", + "Test Folder B,1,login,Steam Account,,\"Test: v\nHidden: asdfer\",0,https://steampowered.com,steam,3Pvb8u7EfbV*nJ,steam://ABCD123", + "", + ].join("\n"); + + assert_eq!(csv, expected); + } + + #[test] + fn test_export_ignore_card() { + let folders = vec![]; + let ciphers = vec![Cipher { + id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), + folder_id: None, + name: "My Card".to_string(), + notes: None, + r#type: CipherType::Card(Box::new(Card { + cardholder_name: None, + exp_month: None, + exp_year: None, + code: None, + brand: None, + number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + deleted_date: None, + }]; + + let csv = export_csv(folders, ciphers).unwrap(); + + assert_eq!(csv, ""); + } + + #[test] + fn test_export_ignore_identity() { + let folders = vec![]; + let ciphers = vec![Cipher { + id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(), + folder_id: None, + name: "My Identity".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: None, + middle_name: None, + last_name: None, + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: None, + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(), + deleted_date: None, + }]; + + let csv = export_csv(folders, ciphers).unwrap(); + + assert_eq!(csv, ""); + } +} diff --git a/crates/bitwarden-exporters/src/encrypted_json.rs b/crates/bitwarden-exporters/src/encrypted_json.rs new file mode 100644 index 000000000..1bbfd2660 --- /dev/null +++ b/crates/bitwarden-exporters/src/encrypted_json.rs @@ -0,0 +1,246 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::{generate_random_bytes, Kdf, KeyEncryptable, PinKey}; +use serde::Serialize; +use thiserror::Error; +use uuid::Uuid; + +use crate::{ + json::{self, export_json}, + Cipher, Folder, +}; + +#[derive(Error, Debug)] +pub enum EncryptedJsonError { + #[error(transparent)] + JsonExport(#[from] json::JsonError), + + #[error("JSON error: {0}")] + Serde(#[from] serde_json::Error), + + #[error("Cryptography error, {0}")] + Crypto(#[from] bitwarden_crypto::CryptoError), +} + +pub(crate) fn export_encrypted_json( + folders: Vec, + ciphers: Vec, + password: String, + kdf: Kdf, +) -> Result { + let decrypted_export = export_json(folders, ciphers)?; + + let (kdf_type, kdf_iterations, kdf_memory, kdf_parallelism) = match kdf { + Kdf::PBKDF2 { iterations } => (0, iterations.get(), None, None), + Kdf::Argon2id { + iterations, + memory, + parallelism, + } => ( + 1, + iterations.get(), + Some(memory.get()), + Some(parallelism.get()), + ), + }; + + let salt: [u8; 16] = generate_random_bytes(); + let salt = STANDARD.encode(salt); + let key = PinKey::derive(password.as_bytes(), salt.as_bytes(), &kdf)?; + + let enc_key_validation = Uuid::new_v4().to_string(); + + let encrypted_export = EncryptedJsonExport { + encrypted: true, + password_protected: true, + salt, + kdf_type, + kdf_iterations, + kdf_memory, + kdf_parallelism, + enc_key_validation: enc_key_validation.encrypt_with_key(&key)?.to_string(), + data: decrypted_export.encrypt_with_key(&key)?.to_string(), + }; + + Ok(serde_json::to_string_pretty(&encrypted_export)?) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct EncryptedJsonExport { + encrypted: bool, + password_protected: bool, + salt: String, + kdf_type: u32, + kdf_iterations: u32, + kdf_memory: Option, + kdf_parallelism: Option, + #[serde(rename = "encKeyValidation_DO_NOT_EDIT")] + enc_key_validation: String, + data: String, +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use super::*; + use crate::{ + Card, Cipher, CipherType, Field, Identity, Login, LoginUri, SecureNote, SecureNoteType, + }; + + #[test] + pub fn test_export() { + let _export = export_encrypted_json( + vec![Folder { + id: "942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap(), + name: "Important".to_string(), + }], + vec![ + Cipher { + id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(), + folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), + + name: "Bitwarden".to_string(), + notes: Some("My note".to_string()), + + r#type: CipherType::Login(Box::new(Login { + username: Some("test@bitwarden.com".to_string()), + password: Some("asdfasdfasdf".to_string()), + login_uris: vec![LoginUri { + uri: Some("https://vault.bitwarden.com".to_string()), + r#match: None, + }], + totp: Some("ABC".to_string()), + })), + + favorite: true, + reprompt: 0, + + fields: vec![ + Field { + name: Some("Text".to_string()), + value: Some("A".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Hidden".to_string()), + value: Some("B".to_string()), + r#type: 1, + linked_id: None, + }, + Field { + name: Some("Boolean (true)".to_string()), + value: Some("true".to_string()), + r#type: 2, + linked_id: None, + }, + Field { + name: Some("Boolean (false)".to_string()), + value: Some("false".to_string()), + r#type: 2, + linked_id: None, + }, + Field { + name: Some("Linked".to_string()), + value: None, + r#type: 3, + linked_id: Some(101), + }, + ], + + revision_date: "2024-01-30T14:09:33.753Z".parse().unwrap(), + creation_date: "2024-01-30T11:23:54.416Z".parse().unwrap(), + deleted_date: None, + }, + Cipher { + id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(), + folder_id: None, + + name: "My secure note".to_string(), + notes: Some("Very secure!".to_string()), + + r#type: CipherType::SecureNote(Box::new(SecureNote { + r#type: SecureNoteType::Generic, + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + deleted_date: None, + }, + Cipher { + id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(), + folder_id: None, + + name: "My card".to_string(), + notes: None, + + r#type: CipherType::Card(Box::new(Card { + cardholder_name: Some("John Doe".to_string()), + exp_month: Some("1".to_string()), + exp_year: Some("2032".to_string()), + code: Some("123".to_string()), + brand: Some("Visa".to_string()), + number: Some("4111111111111111".to_string()), + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + deleted_date: None, + }, + Cipher { + id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(), + folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), + + name: "My identity".to_string(), + notes: None, + + r#type: CipherType::Identity(Box::new(Identity { + title: Some("Mr".to_string()), + first_name: Some("John".to_string()), + middle_name: None, + last_name: Some("Doe".to_string()), + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: Some("Bitwarden".to_string()), + email: None, + phone: None, + ssn: None, + username: Some("JDoe".to_string()), + passport_number: None, + license_number: None, + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T17:54:50.706Z".parse().unwrap(), + creation_date: "2024-01-30T17:54:50.706Z".parse().unwrap(), + deleted_date: None, + }, + ], + "password".to_string(), + Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }, + ) + .unwrap(); + } +} diff --git a/crates/bitwarden-exporters/src/json.rs b/crates/bitwarden-exporters/src/json.rs new file mode 100644 index 000000000..3f6c72c1f --- /dev/null +++ b/crates/bitwarden-exporters/src/json.rs @@ -0,0 +1,762 @@ +use chrono::{DateTime, Utc}; +use thiserror::Error; +use uuid::Uuid; + +use crate::{Card, Cipher, CipherType, Field, Folder, Identity, Login, LoginUri, SecureNote}; + +#[derive(Error, Debug)] +pub enum JsonError { + #[error("JSON error: {0}")] + Serde(#[from] serde_json::Error), +} + +pub(crate) fn export_json(folders: Vec, ciphers: Vec) -> Result { + let export = JsonExport { + encrypted: false, + folders: folders.into_iter().map(|f| f.into()).collect(), + items: ciphers.into_iter().map(|c| c.into()).collect(), + }; + + Ok(serde_json::to_string_pretty(&export)?) +} + +/// JSON export format. These are intentionally decoupled from the internal data structures to +/// ensure internal changes are not reflected in the public exports. +/// +/// Be careful about changing these structs to maintain compatibility with old exporters/importers. +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonExport { + encrypted: bool, + folders: Vec, + items: Vec, +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonFolder { + id: Uuid, + name: String, +} + +impl From for JsonFolder { + fn from(folder: Folder) -> Self { + JsonFolder { + id: folder.id, + name: folder.name, + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonCipher { + id: Uuid, + folder_id: Option, + // Organizational IDs which are always empty in personal exports + organization_id: Option, + collection_ids: Option>, + + name: String, + notes: Option, + + r#type: u8, + #[serde(skip_serializing_if = "Option::is_none")] + login: Option, + #[serde(skip_serializing_if = "Option::is_none")] + identity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + card: Option, + #[serde(skip_serializing_if = "Option::is_none")] + secure_note: Option, + + favorite: bool, + reprompt: u8, + + #[serde(skip_serializing_if = "Vec::is_empty")] + fields: Vec, + password_history: Option>, + + revision_date: DateTime, + creation_date: DateTime, + deleted_date: Option>, +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonLogin { + username: Option, + password: Option, + uris: Vec, + totp: Option, + fido2_credentials: Vec, +} + +impl From for JsonLogin { + fn from(login: Login) -> Self { + JsonLogin { + username: login.username, + password: login.password, + uris: login.login_uris.into_iter().map(|u| u.into()).collect(), + totp: login.totp, + fido2_credentials: vec![], + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonLoginUri { + uri: Option, + r#match: Option, +} + +impl From for JsonLoginUri { + fn from(login_uri: LoginUri) -> Self { + JsonLoginUri { + uri: login_uri.uri, + r#match: login_uri.r#match, + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonSecureNote { + r#type: u8, +} + +impl From for JsonSecureNote { + fn from(note: SecureNote) -> Self { + JsonSecureNote { + r#type: note.r#type as u8, + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonCard { + cardholder_name: Option, + exp_month: Option, + exp_year: Option, + code: Option, + brand: Option, + number: Option, +} + +impl From for JsonCard { + fn from(card: Card) -> Self { + JsonCard { + cardholder_name: card.cardholder_name, + exp_month: card.exp_month, + exp_year: card.exp_year, + code: card.code, + brand: card.brand, + number: card.number, + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonIdentity { + title: Option, + first_name: Option, + middle_name: Option, + last_name: Option, + address1: Option, + address2: Option, + address3: Option, + city: Option, + state: Option, + postal_code: Option, + country: Option, + company: Option, + email: Option, + phone: Option, + ssn: Option, + username: Option, + passport_number: Option, + license_number: Option, +} + +impl From for JsonIdentity { + fn from(identity: Identity) -> Self { + JsonIdentity { + title: identity.title, + first_name: identity.first_name, + middle_name: identity.middle_name, + last_name: identity.last_name, + address1: identity.address1, + address2: identity.address2, + address3: identity.address3, + city: identity.city, + state: identity.state, + postal_code: identity.postal_code, + country: identity.country, + company: identity.company, + email: identity.email, + phone: identity.phone, + ssn: identity.ssn, + username: identity.username, + passport_number: identity.passport_number, + license_number: identity.license_number, + } + } +} + +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonField { + name: Option, + value: Option, + r#type: u8, + linked_id: Option, +} + +impl From for JsonField { + fn from(field: Field) -> Self { + JsonField { + name: field.name, + value: field.value, + r#type: field.r#type, + linked_id: field.linked_id, + } + } +} + +impl From for JsonCipher { + fn from(cipher: Cipher) -> Self { + let r#type = match cipher.r#type { + CipherType::Login(_) => 1, + CipherType::SecureNote(_) => 2, + CipherType::Card(_) => 3, + CipherType::Identity(_) => 4, + }; + + let (login, secure_note, card, identity) = match cipher.r#type { + CipherType::Login(l) => (Some((*l).into()), None, None, None), + CipherType::SecureNote(s) => (None, Some((*s).into()), None, None), + CipherType::Card(c) => (None, None, Some((*c).into()), None), + CipherType::Identity(i) => (None, None, None, Some((*i).into())), + }; + + JsonCipher { + id: cipher.id, + folder_id: cipher.folder_id, + organization_id: None, + collection_ids: None, + name: cipher.name, + notes: cipher.notes, + r#type, + login, + identity, + card, + secure_note, + favorite: cipher.favorite, + reprompt: cipher.reprompt, + fields: cipher.fields.into_iter().map(|f| f.into()).collect(), + password_history: None, + revision_date: cipher.revision_date, + creation_date: cipher.creation_date, + deleted_date: cipher.deleted_date, + } + } +} + +#[cfg(test)] +mod tests { + use std::{fs, io::Read, path::PathBuf}; + + use super::*; + use crate::{Cipher, Field, LoginUri, SecureNoteType}; + + #[test] + fn test_convert_login() { + let cipher = Cipher { + id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(), + folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), + + name: "Bitwarden".to_string(), + notes: Some("My note".to_string()), + + r#type: CipherType::Login(Box::new(Login { + username: Some("test@bitwarden.com".to_string()), + password: Some("asdfasdfasdf".to_string()), + login_uris: vec![LoginUri { + uri: Some("https://vault.bitwarden.com".to_string()), + r#match: None, + }], + totp: Some("ABC".to_string()), + })), + + favorite: true, + reprompt: 0, + + fields: vec![ + Field { + name: Some("Text".to_string()), + value: Some("A".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Hidden".to_string()), + value: Some("B".to_string()), + r#type: 1, + linked_id: None, + }, + Field { + name: Some("Boolean (true)".to_string()), + value: Some("true".to_string()), + r#type: 2, + linked_id: None, + }, + Field { + name: Some("Boolean (false)".to_string()), + value: Some("false".to_string()), + r#type: 2, + linked_id: None, + }, + Field { + name: Some("Linked".to_string()), + value: None, + r#type: 3, + linked_id: Some(101), + }, + ], + + revision_date: "2024-01-30T14:09:33.753Z".parse().unwrap(), + creation_date: "2024-01-30T11:23:54.416Z".parse().unwrap(), + deleted_date: None, + }; + + let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap(); + + let expected = r#"{ + "passwordHistory": null, + "revisionDate": "2024-01-30T14:09:33.753Z", + "creationDate": "2024-01-30T11:23:54.416Z", + "deletedDate": null, + "id": "25c8c414-b446-48e9-a1bd-b10700bbd740", + "organizationId": null, + "folderId": "942e2984-1b9a-453b-b039-b107012713b9", + "type": 1, + "reprompt": 0, + "name": "Bitwarden", + "notes": "My note", + "favorite": true, + "fields": [ + { + "name": "Text", + "value": "A", + "type": 0, + "linkedId": null + }, + { + "name": "Hidden", + "value": "B", + "type": 1, + "linkedId": null + }, + { + "name": "Boolean (true)", + "value": "true", + "type": 2, + "linkedId": null + }, + { + "name": "Boolean (false)", + "value": "false", + "type": 2, + "linkedId": null + }, + { + "name": "Linked", + "value": null, + "type": 3, + "linkedId": 101 + } + ], + "login": { + "fido2Credentials": [], + "uris": [ + { + "match": null, + "uri": "https://vault.bitwarden.com" + } + ], + "username": "test@bitwarden.com", + "password": "asdfasdfasdf", + "totp": "ABC" + }, + "collectionIds": null + }"#; + + assert_eq!( + json.parse::().unwrap(), + expected.parse::().unwrap() + ) + } + + #[test] + fn test_convert_secure_note() { + let cipher = Cipher { + id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(), + folder_id: None, + + name: "My secure note".to_string(), + notes: Some("Very secure!".to_string()), + + r#type: CipherType::SecureNote(Box::new(SecureNote { + r#type: SecureNoteType::Generic, + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + deleted_date: None, + }; + + let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap(); + + let expected = r#"{ + "passwordHistory": null, + "revisionDate": "2024-01-30T11:25:25.466Z", + "creationDate": "2024-01-30T11:25:25.466Z", + "deletedDate": null, + "id": "23f0f877-42b1-4820-a850-b10700bc41eb", + "organizationId": null, + "folderId": null, + "type": 2, + "reprompt": 0, + "name": "My secure note", + "notes": "Very secure!", + "favorite": false, + "secureNote": { + "type": 0 + }, + "collectionIds": null + }"#; + + assert_eq!( + json.parse::().unwrap(), + expected.parse::().unwrap() + ) + } + + #[test] + fn test_convert_card() { + let cipher = Cipher { + id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(), + folder_id: None, + + name: "My card".to_string(), + notes: None, + + r#type: CipherType::Card(Box::new(Card { + cardholder_name: Some("John Doe".to_string()), + exp_month: Some("1".to_string()), + exp_year: Some("2032".to_string()), + code: Some("123".to_string()), + brand: Some("Visa".to_string()), + number: Some("4111111111111111".to_string()), + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + deleted_date: None, + }; + + let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap(); + + let expected = r#"{ + "passwordHistory": null, + "revisionDate": "2024-01-30T17:55:36.150Z", + "creationDate": "2024-01-30T17:55:36.150Z", + "deletedDate": null, + "id": "3ed8de45-48ee-4e26-a2dc-b10701276c53", + "organizationId": null, + "folderId": null, + "type": 3, + "reprompt": 0, + "name": "My card", + "notes": null, + "favorite": false, + "card": { + "cardholderName": "John Doe", + "brand": "Visa", + "number": "4111111111111111", + "expMonth": "1", + "expYear": "2032", + "code": "123" + }, + "collectionIds": null + }"#; + + assert_eq!( + json.parse::().unwrap(), + expected.parse::().unwrap() + ) + } + + #[test] + fn test_convert_identity() { + let cipher = Cipher { + id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(), + folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), + + name: "My identity".to_string(), + notes: None, + + r#type: CipherType::Identity(Box::new(Identity { + title: Some("Mr".to_string()), + first_name: Some("John".to_string()), + middle_name: None, + last_name: Some("Doe".to_string()), + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: Some("Bitwarden".to_string()), + email: None, + phone: None, + ssn: None, + username: Some("JDoe".to_string()), + passport_number: None, + license_number: None, + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T17:54:50.706Z".parse().unwrap(), + creation_date: "2024-01-30T17:54:50.706Z".parse().unwrap(), + deleted_date: None, + }; + + let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap(); + + let expected = r#"{ + "passwordHistory": null, + "revisionDate": "2024-01-30T17:54:50.706Z", + "creationDate": "2024-01-30T17:54:50.706Z", + "deletedDate": null, + "id": "41cc3bc1-c3d9-4637-876c-b10701273712", + "organizationId": null, + "folderId": "942e2984-1b9a-453b-b039-b107012713b9", + "type": 4, + "reprompt": 0, + "name": "My identity", + "notes": null, + "favorite": false, + "identity": { + "title": "Mr", + "firstName": "John", + "middleName": null, + "lastName": "Doe", + "address1": null, + "address2": null, + "address3": null, + "city": null, + "state": null, + "postalCode": null, + "country": null, + "company": "Bitwarden", + "email": null, + "phone": null, + "ssn": null, + "username": "JDoe", + "passportNumber": null, + "licenseNumber": null + }, + "collectionIds": null + }"#; + + assert_eq!( + json.parse::().unwrap(), + expected.parse::().unwrap() + ) + } + + #[test] + pub fn test_export() { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources"); + d.push("json_export.json"); + + let mut file = fs::File::open(d).unwrap(); + + let mut expected = String::new(); + file.read_to_string(&mut expected).unwrap(); + + let export = export_json( + vec![Folder { + id: "942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap(), + name: "Important".to_string(), + }], + vec![ + Cipher { + id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(), + folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), + + name: "Bitwarden".to_string(), + notes: Some("My note".to_string()), + + r#type: CipherType::Login(Box::new(Login { + username: Some("test@bitwarden.com".to_string()), + password: Some("asdfasdfasdf".to_string()), + login_uris: vec![LoginUri { + uri: Some("https://vault.bitwarden.com".to_string()), + r#match: None, + }], + totp: Some("ABC".to_string()), + })), + + favorite: true, + reprompt: 0, + + fields: vec![ + Field { + name: Some("Text".to_string()), + value: Some("A".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Hidden".to_string()), + value: Some("B".to_string()), + r#type: 1, + linked_id: None, + }, + Field { + name: Some("Boolean (true)".to_string()), + value: Some("true".to_string()), + r#type: 2, + linked_id: None, + }, + Field { + name: Some("Boolean (false)".to_string()), + value: Some("false".to_string()), + r#type: 2, + linked_id: None, + }, + Field { + name: Some("Linked".to_string()), + value: None, + r#type: 3, + linked_id: Some(101), + }, + ], + + revision_date: "2024-01-30T14:09:33.753Z".parse().unwrap(), + creation_date: "2024-01-30T11:23:54.416Z".parse().unwrap(), + deleted_date: None, + }, + Cipher { + id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(), + folder_id: None, + + name: "My secure note".to_string(), + notes: Some("Very secure!".to_string()), + + r#type: CipherType::SecureNote(Box::new(SecureNote { + r#type: SecureNoteType::Generic, + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + deleted_date: None, + }, + Cipher { + id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(), + folder_id: None, + + name: "My card".to_string(), + notes: None, + + r#type: CipherType::Card(Box::new(Card { + cardholder_name: Some("John Doe".to_string()), + exp_month: Some("1".to_string()), + exp_year: Some("2032".to_string()), + code: Some("123".to_string()), + brand: Some("Visa".to_string()), + number: Some("4111111111111111".to_string()), + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + deleted_date: None, + }, + Cipher { + id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(), + folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()), + + name: "My identity".to_string(), + notes: None, + + r#type: CipherType::Identity(Box::new(Identity { + title: Some("Mr".to_string()), + first_name: Some("John".to_string()), + middle_name: None, + last_name: Some("Doe".to_string()), + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: Some("Bitwarden".to_string()), + email: None, + phone: None, + ssn: None, + username: Some("JDoe".to_string()), + passport_number: None, + license_number: None, + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T17:54:50.706Z".parse().unwrap(), + creation_date: "2024-01-30T17:54:50.706Z".parse().unwrap(), + deleted_date: None, + }, + ], + ) + .unwrap(); + + assert_eq!( + export.parse::().unwrap(), + expected.parse::().unwrap() + ) + } +} diff --git a/crates/bitwarden-exporters/src/lib.rs b/crates/bitwarden-exporters/src/lib.rs new file mode 100644 index 000000000..f17d31a2d --- /dev/null +++ b/crates/bitwarden-exporters/src/lib.rs @@ -0,0 +1,149 @@ +use bitwarden_crypto::Kdf; +use chrono::{DateTime, Utc}; +use thiserror::Error; +use uuid::Uuid; + +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 { + Csv, + Json, + EncryptedJson { password: String, kdf: Kdf }, +} + +/// Export representation of a Bitwarden folder. +/// +/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API +/// that is not tied to the internal vault models. We may revisit this in the future. +pub struct Folder { + pub id: Uuid, + pub name: String, +} + +/// Export representation of a Bitwarden cipher. +/// +/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API +/// that is not tied to the internal vault models. We may revisit this in the future. +pub struct Cipher { + pub id: Uuid, + pub folder_id: Option, + + pub name: String, + pub notes: Option, + + pub r#type: CipherType, + + pub favorite: bool, + pub reprompt: u8, + + pub fields: Vec, + + pub revision_date: DateTime, + pub creation_date: DateTime, + pub deleted_date: Option>, +} + +#[derive(Clone)] +pub struct Field { + pub name: Option, + pub value: Option, + pub r#type: u8, + pub linked_id: Option, +} + +pub enum CipherType { + Login(Box), + SecureNote(Box), + Card(Box), + Identity(Box), +} + +impl ToString for CipherType { + fn to_string(&self) -> String { + match self { + CipherType::Login(_) => "login".to_string(), + CipherType::SecureNote(_) => "note".to_string(), + CipherType::Card(_) => "card".to_string(), + CipherType::Identity(_) => "identity".to_string(), + } + } +} + +pub struct Login { + pub username: Option, + pub password: Option, + pub login_uris: Vec, + pub totp: Option, +} + +pub struct LoginUri { + pub uri: Option, + pub r#match: Option, +} + +pub struct Card { + pub cardholder_name: Option, + pub exp_month: Option, + pub exp_year: Option, + pub code: Option, + pub brand: Option, + pub number: Option, +} + +pub struct SecureNote { + pub r#type: SecureNoteType, +} + +pub enum SecureNoteType { + Generic = 0, +} + +pub struct Identity { + pub title: Option, + pub first_name: Option, + pub middle_name: Option, + pub last_name: Option, + pub address1: Option, + pub address2: Option, + pub address3: Option, + pub city: Option, + pub state: Option, + pub postal_code: Option, + pub country: Option, + pub company: Option, + pub email: Option, + pub phone: Option, + pub ssn: Option, + pub username: Option, + 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-generators/Cargo.toml b/crates/bitwarden-generators/Cargo.toml new file mode 100644 index 000000000..3f79ac539 --- /dev/null +++ b/crates/bitwarden-generators/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "bitwarden-generators" +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] +mobile = ["dep:uniffi"] # Mobile-specific features + +[dependencies] +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.26.1", optional = true } + +[dev-dependencies] +rand_chacha = "0.3.1" +tokio = { version = "1.36.0", features = ["rt", "macros"] } +wiremock = "0.6.0" + +[lints] +workspace = true diff --git a/crates/bitwarden-generators/README.md b/crates/bitwarden-generators/README.md new file mode 100644 index 000000000..db70c11df --- /dev/null +++ b/crates/bitwarden-generators/README.md @@ -0,0 +1,6 @@ +# Bitwarden Generators + +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-generators/src/lib.rs b/crates/bitwarden-generators/src/lib.rs new file mode 100644 index 000000000..335ec92b9 --- /dev/null +++ b/crates/bitwarden-generators/src/lib.rs @@ -0,0 +1,11 @@ +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 username_forwarders; + +#[cfg(feature = "mobile")] +uniffi::setup_scaffolding!(); diff --git a/crates/bitwarden-generators/src/passphrase.rs b/crates/bitwarden-generators/src/passphrase.rs new file mode 100644 index 000000000..d99728219 --- /dev/null +++ b/crates/bitwarden-generators/src/passphrase.rs @@ -0,0 +1,229 @@ +use bitwarden_crypto::EFF_LONG_WORD_LIST; +use rand::{seq::SliceRandom, Rng, RngCore}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::util::capitalize_first_letter; + +#[derive(Debug, Error)] +pub enum PassphraseError { + #[error("'num_words' must be between {} and {}", minimum, maximum)] + InvalidNumWords { minimum: u8, maximum: u8 }, +} + +/// Passphrase generator request options. +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct PassphraseGeneratorRequest { + /// Number of words in the generated passphrase. + /// This value must be between 3 and 20. + pub num_words: u8, + /// Character separator between words in the generated passphrase. The value cannot be empty. + pub word_separator: String, + /// When set to true, capitalize the first letter of each word in the generated passphrase. + pub capitalize: bool, + /// When set to true, include a number at the end of one of the words in the generated + /// passphrase. + pub include_number: bool, +} + +impl Default for PassphraseGeneratorRequest { + fn default() -> Self { + Self { + num_words: 3, + word_separator: ' '.to_string(), + capitalize: false, + include_number: false, + } + } +} + +const MINIMUM_PASSPHRASE_NUM_WORDS: u8 = 3; +const MAXIMUM_PASSPHRASE_NUM_WORDS: u8 = 20; + +/// Represents a set of valid options to generate a passhprase with. +/// To get an instance of it, use +/// [`PassphraseGeneratorRequest::validate_options`](PassphraseGeneratorRequest::validate_options) +struct ValidPassphraseGeneratorOptions { + pub(super) num_words: u8, + pub(super) word_separator: String, + pub(super) capitalize: bool, + pub(super) include_number: bool, +} + +impl PassphraseGeneratorRequest { + /// Validates the request and returns an immutable struct with valid options to use with the + /// passphrase generator. + fn validate_options(self) -> Result { + // TODO: Add password generator policy checks + + if !(MINIMUM_PASSPHRASE_NUM_WORDS..=MAXIMUM_PASSPHRASE_NUM_WORDS).contains(&self.num_words) + { + return Err(PassphraseError::InvalidNumWords { + minimum: MINIMUM_PASSPHRASE_NUM_WORDS, + maximum: MAXIMUM_PASSPHRASE_NUM_WORDS, + }); + } + + Ok(ValidPassphraseGeneratorOptions { + num_words: self.num_words, + word_separator: self.word_separator, + capitalize: self.capitalize, + include_number: self.include_number, + }) + } +} + +/// Implementation of the random passphrase generator. +pub fn passphrase(request: PassphraseGeneratorRequest) -> Result { + let options = request.validate_options()?; + Ok(passphrase_with_rng(rand::thread_rng(), options)) +} + +fn passphrase_with_rng(mut rng: impl RngCore, options: ValidPassphraseGeneratorOptions) -> String { + let mut passphrase_words = gen_words(&mut rng, options.num_words); + if options.include_number { + include_number_in_words(&mut rng, &mut passphrase_words); + } + if options.capitalize { + capitalize_words(&mut passphrase_words); + } + passphrase_words.join(&options.word_separator) +} + +fn gen_words(mut rng: impl RngCore, num_words: u8) -> Vec { + (0..num_words) + .map(|_| { + EFF_LONG_WORD_LIST + .choose(&mut rng) + .expect("slice is not empty") + .to_string() + }) + .collect() +} + +fn include_number_in_words(mut rng: impl RngCore, words: &mut [String]) { + let number_idx = rng.gen_range(0..words.len()); + words[number_idx].push_str(&rng.gen_range(0..=9).to_string()); +} + +fn capitalize_words(words: &mut [String]) { + words + .iter_mut() + .for_each(|w| *w = capitalize_first_letter(w)); +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + + use super::*; + + #[test] + fn test_gen_words() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + assert_eq!( + &gen_words(&mut rng, 4), + &["subsystem", "undertook", "silenced", "dinginess"] + ); + assert_eq!(&gen_words(&mut rng, 1), &["numbing"]); + assert_eq!(&gen_words(&mut rng, 2), &["catnip", "jokester"]); + } + + #[test] + fn test_capitalize() { + assert_eq!(capitalize_first_letter("hello"), "Hello"); + assert_eq!(capitalize_first_letter("1ello"), "1ello"); + assert_eq!(capitalize_first_letter("Hello"), "Hello"); + assert_eq!(capitalize_first_letter("h"), "H"); + assert_eq!(capitalize_first_letter(""), ""); + + // Also supports non-ascii, though the EFF list doesn't have any + assert_eq!(capitalize_first_letter("áéíóú"), "Áéíóú"); + } + + #[test] + fn test_capitalize_words() { + let mut words = vec!["hello".into(), "world".into()]; + capitalize_words(&mut words); + assert_eq!(words, &["Hello", "World"]); + } + + #[test] + fn test_include_number() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let mut words = vec!["hello".into(), "world".into()]; + include_number_in_words(&mut rng, &mut words); + assert_eq!(words, &["hello", "world7"]); + + let mut words = vec!["This".into(), "is".into(), "a".into(), "test".into()]; + include_number_in_words(&mut rng, &mut words); + assert_eq!(words, &["This", "is", "a1", "test"]); + } + + #[test] + fn test_separator() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let input = PassphraseGeneratorRequest { + num_words: 4, + word_separator: "👨🏻‍❤️‍💋‍👨🏻".into(), /* This emoji is 35 bytes long, but represented + * as a single character */ + capitalize: false, + include_number: true, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "subsystem4👨🏻‍❤️‍💋‍👨🏻undertook👨🏻‍❤️‍💋‍👨🏻silenced👨🏻‍❤️‍💋‍👨🏻dinginess" + ); + } + + #[test] + fn test_passphrase() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let input = PassphraseGeneratorRequest { + num_words: 4, + word_separator: "-".into(), + capitalize: true, + include_number: true, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "Subsystem4-Undertook-Silenced-Dinginess" + ); + + let input = PassphraseGeneratorRequest { + num_words: 3, + word_separator: " ".into(), + capitalize: false, + include_number: true, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "drew7 hankering cabana" + ); + + let input = PassphraseGeneratorRequest { + num_words: 5, + word_separator: ";".into(), + capitalize: false, + include_number: false, + } + .validate_options() + .unwrap(); + assert_eq!( + passphrase_with_rng(&mut rng, input), + "duller;backlight;factual;husked;remover" + ); + } +} diff --git a/crates/bitwarden-generators/src/password.rs b/crates/bitwarden-generators/src/password.rs new file mode 100644 index 000000000..6cab6dd65 --- /dev/null +++ b/crates/bitwarden-generators/src/password.rs @@ -0,0 +1,397 @@ +use std::collections::BTreeSet; + +use rand::{distributions::Distribution, seq::SliceRandom, RngCore}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum PasswordError { + #[error("No character set enabled")] + NoCharacterSetEnabled, + #[error("Invalid password length")] + InvalidLength, +} + +/// Password generator request options. +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct PasswordGeneratorRequest { + /// Include lowercase characters (a-z). + pub lowercase: bool, + /// Include uppercase characters (A-Z). + pub uppercase: bool, + /// Include numbers (0-9). + pub numbers: bool, + /// Include special characters: ! @ # $ % ^ & * + pub special: bool, + + /// The length of the generated password. + /// Note that the password length must be greater than the sum of all the minimums. + pub length: u8, + + /// When set to true, the generated password will not contain ambiguous characters. + /// The ambiguous characters are: I, O, l, 0, 1 + pub avoid_ambiguous: bool, // TODO: Should we rename this to include_all_characters? + + /// The minimum number of lowercase characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is lowercase is false + pub min_lowercase: Option, + /// The minimum number of uppercase characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is uppercase is false + pub min_uppercase: Option, + /// The minimum number of numbers in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is numbers is false + pub min_number: Option, + /// The minimum number of special characters in the generated password. + /// When set, the value must be between 1 and 9. This value is ignored is special is false + pub min_special: Option, +} + +const DEFAULT_PASSWORD_LENGTH: u8 = 16; + +impl Default for PasswordGeneratorRequest { + fn default() -> Self { + Self { + lowercase: true, + uppercase: true, + numbers: true, + special: false, + length: DEFAULT_PASSWORD_LENGTH, + avoid_ambiguous: false, + min_lowercase: None, + min_uppercase: None, + min_number: None, + min_special: None, + } + } +} + +const UPPER_CHARS_AMBIGUOUS: &[char] = &['I', 'O']; +const LOWER_CHARS_AMBIGUOUS: &[char] = &['l']; +const NUMBER_CHARS_AMBIGUOUS: &[char] = &['0', '1']; +const SPECIAL_CHARS: &[char] = &['!', '@', '#', '$', '%', '^', '&', '*']; + +/// A set of characters used to generate a password. This set is backed by a BTreeSet +/// to have consistent ordering between runs. This is not important during normal execution, +/// but it's necessary for the tests to be repeatable. +/// To create an instance, use [`CharSet::default()`](CharSet::default) +#[derive(Clone, Default)] +struct CharSet(BTreeSet); +impl CharSet { + /// Includes the given characters in the set. Any duplicate items will be ignored + pub fn include(self, other: impl IntoIterator) -> Self { + self.include_if(true, other) + } + + /// Includes the given characters in the set if the predicate is true. Any duplicate items will + /// be ignored + pub fn include_if(mut self, predicate: bool, other: impl IntoIterator) -> Self { + if predicate { + self.0.extend(other); + } + self + } + + /// Excludes the given characters from the set. Any missing items will be ignored + pub fn exclude_if<'a>( + self, + predicate: bool, + other: impl IntoIterator, + ) -> Self { + if predicate { + let other: BTreeSet<_> = other.into_iter().copied().collect(); + Self(self.0.difference(&other).copied().collect()) + } else { + self + } + } +} +impl<'a> IntoIterator for &'a CharSet { + type Item = char; + type IntoIter = std::iter::Copied>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter().copied() + } +} +impl Distribution for CharSet { + fn sample(&self, rng: &mut R) -> char { + let idx = rng.gen_range(0..self.0.len()); + *self.0.iter().nth(idx).expect("Valid index") + } +} + +/// Represents a set of valid options to generate a password with. +/// To get an instance of it, use +/// [`PasswordGeneratorRequest::validate_options`](PasswordGeneratorRequest::validate_options) +struct PasswordGeneratorOptions { + pub(super) lower: (CharSet, usize), + pub(super) upper: (CharSet, usize), + pub(super) number: (CharSet, usize), + pub(super) special: (CharSet, usize), + pub(super) all: (CharSet, usize), + + pub(super) length: usize, +} + +impl PasswordGeneratorRequest { + /// Validates the request and returns an immutable struct with valid options to use with the + /// password generator. + fn validate_options(self) -> Result { + // TODO: Add password generator policy checks + + // We always have to have at least one character set enabled + if !self.lowercase && !self.uppercase && !self.numbers && !self.special { + return Err(PasswordError::NoCharacterSetEnabled); + } + + if self.length < 4 { + return Err(PasswordError::InvalidLength); + } + + // Make sure the minimum values are zero when the character + // set is disabled, and at least one when it's enabled + fn get_minimum(min: Option, enabled: bool) -> usize { + if enabled { + usize::max(min.unwrap_or(1) as usize, 1) + } else { + 0 + } + } + + let length = self.length as usize; + let min_lowercase = get_minimum(self.min_lowercase, self.lowercase); + let min_uppercase = get_minimum(self.min_uppercase, self.uppercase); + let min_number = get_minimum(self.min_number, self.numbers); + let min_special = get_minimum(self.min_special, self.special); + + // Check that the minimum lengths aren't larger than the password length + let minimum_length = min_lowercase + min_uppercase + min_number + min_special; + if minimum_length > length { + return Err(PasswordError::InvalidLength); + } + + let lower = ( + CharSet::default() + .include_if(self.lowercase, 'a'..='z') + .exclude_if(self.avoid_ambiguous, LOWER_CHARS_AMBIGUOUS), + min_lowercase, + ); + + let upper = ( + CharSet::default() + .include_if(self.uppercase, 'A'..='Z') + .exclude_if(self.avoid_ambiguous, UPPER_CHARS_AMBIGUOUS), + min_uppercase, + ); + + let number = ( + CharSet::default() + .include_if(self.numbers, '0'..='9') + .exclude_if(self.avoid_ambiguous, NUMBER_CHARS_AMBIGUOUS), + min_number, + ); + + let special = ( + CharSet::default().include_if(self.special, SPECIAL_CHARS.iter().copied()), + min_special, + ); + + let all = ( + CharSet::default() + .include(&lower.0) + .include(&upper.0) + .include(&number.0) + .include(&special.0), + length - minimum_length, + ); + + Ok(PasswordGeneratorOptions { + lower, + upper, + number, + special, + all, + length, + }) + } +} + +/// Implementation of the random password generator. +pub fn password(input: PasswordGeneratorRequest) -> Result { + let options = input.validate_options()?; + Ok(password_with_rng(rand::thread_rng(), options)) +} + +fn password_with_rng(mut rng: impl RngCore, options: PasswordGeneratorOptions) -> String { + let mut buf: Vec = Vec::with_capacity(options.length); + + let opts = [ + &options.all, + &options.upper, + &options.lower, + &options.number, + &options.special, + ]; + for (set, qty) in opts { + buf.extend(set.sample_iter(&mut rng).take(*qty)); + } + + buf.shuffle(&mut rng); + + buf.iter().collect() +} + +#[cfg(test)] +mod test { + use std::collections::BTreeSet; + + use rand::SeedableRng; + + use super::*; + + // We convert the slices to BTreeSets to be able to use `is_subset` + fn ref_to_set<'a>(chars: impl IntoIterator) -> BTreeSet { + chars.into_iter().copied().collect() + } + fn to_set(chars: impl IntoIterator) -> BTreeSet { + chars.into_iter().collect() + } + + #[test] + fn test_password_gen_all_charsets_enabled() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: true, + special: true, + avoid_ambiguous: false, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set('0'..='9')); + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "Z!^B5r%hUa23dFM@"); + } + + #[test] + fn test_password_gen_only_letters_enabled() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: false, + special: false, + avoid_ambiguous: false, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set([])); + assert_eq!(to_set(&options.special.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "NQiFrGufQMiNUAmj"); + } + + #[test] + fn test_password_gen_only_numbers_and_lower_enabled_no_ambiguous() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: false, + numbers: true, + special: false, + avoid_ambiguous: true, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert!(to_set(&options.lower.0).is_subset(&to_set('a'..='z'))); + assert!(to_set(&options.lower.0).is_disjoint(&ref_to_set(LOWER_CHARS_AMBIGUOUS))); + + assert!(to_set(&options.number.0).is_subset(&to_set('0'..='9'))); + assert!(to_set(&options.number.0).is_disjoint(&ref_to_set(NUMBER_CHARS_AMBIGUOUS))); + + assert_eq!(to_set(&options.upper.0), to_set([])); + assert_eq!(to_set(&options.special.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "mnjabfz5ct272prf"); + } + + #[test] + fn test_password_gen_only_upper_and_special_enabled_no_ambiguous() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: false, + uppercase: true, + numbers: false, + special: true, + avoid_ambiguous: true, + ..Default::default() + } + .validate_options() + .unwrap(); + + assert!(to_set(&options.upper.0).is_subset(&to_set('A'..='Z'))); + assert!(to_set(&options.upper.0).is_disjoint(&ref_to_set(UPPER_CHARS_AMBIGUOUS))); + + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + assert_eq!(to_set(&options.lower.0), to_set([])); + assert_eq!(to_set(&options.number.0), to_set([])); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "B*GBQANS%UZPQD!K"); + } + + #[test] + fn test_password_gen_minimum_limits() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + + let options = PasswordGeneratorRequest { + lowercase: true, + uppercase: true, + numbers: true, + special: true, + avoid_ambiguous: false, + length: 24, + min_lowercase: Some(5), + min_uppercase: Some(5), + min_number: Some(5), + min_special: Some(5), + } + .validate_options() + .unwrap(); + + assert_eq!(to_set(&options.lower.0), to_set('a'..='z')); + assert_eq!(to_set(&options.upper.0), to_set('A'..='Z')); + assert_eq!(to_set(&options.number.0), to_set('0'..='9')); + assert_eq!(to_set(&options.special.0), ref_to_set(SPECIAL_CHARS)); + + assert_eq!(options.lower.1, 5); + assert_eq!(options.upper.1, 5); + assert_eq!(options.number.1, 5); + assert_eq!(options.special.1, 5); + + let pass = password_with_rng(&mut rng, options); + assert_eq!(pass, "236q5!a#R%PG5rI%k1!*@uRt"); + } +} diff --git a/crates/bitwarden-generators/src/username.rs b/crates/bitwarden-generators/src/username.rs new file mode 100644 index 000000000..ccb46604b --- /dev/null +++ b/crates/bitwarden-generators/src/username.rs @@ -0,0 +1,267 @@ +use bitwarden_crypto::EFF_LONG_WORD_LIST; +use rand::{distributions::Distribution, seq::SliceRandom, Rng, RngCore}; +use reqwest::StatusCode; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::util::capitalize_first_letter; + +#[derive(Debug, Error)] +pub enum UsernameError { + #[error("Invalid API Key")] + InvalidApiKey, + #[error("Unknown error")] + Unknown, + + #[error("Received error message from server: [{}] {}", .status, .message)] + ResponseContent { status: StatusCode, message: String }, + + #[error(transparent)] + Reqwest(#[from] reqwest::Error), +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum AppendType { + /// Generates a random string of 8 lowercase characters as part of your username + Random, + /// Uses the websitename as part of your username + WebsiteName { website: String }, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +/// Configures the email forwarding service to use. +/// For instructions on how to configure each service, see the documentation: +/// +pub enum ForwarderServiceType { + /// Previously known as "AnonAddy" + AddyIo { + api_token: String, + domain: String, + base_url: String, + }, + DuckDuckGo { + token: String, + }, + Firefox { + api_token: String, + }, + Fastmail { + api_token: String, + }, + ForwardEmail { + api_token: String, + domain: String, + }, + SimpleLogin { + api_key: String, + }, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum UsernameGeneratorRequest { + /// Generates a single word username + Word { + /// Capitalize the first letter of the word + capitalize: bool, + /// Include a 4 digit number at the end of the word + include_number: bool, + }, + /// Generates an email using your provider's subaddressing capabilities. + /// Note that not all providers support this functionality. + /// This will generate an address of the format `youremail+generated@domain.tld` + Subaddress { + /// The type of subaddress to add to the base email + r#type: AppendType, + /// The full email address to use as the base for the subaddress + email: String, + }, + Catchall { + /// The type of username to use with the catchall email domain + r#type: AppendType, + /// The domain to use for the catchall email address + domain: String, + }, + Forwarded { + /// The email forwarding service to use, see [ForwarderServiceType] + /// for instructions on how to configure each + service: ForwarderServiceType, + /// The website for which the email address is being generated + /// This is not used in all services, and is only used for display purposes + website: Option, + }, +} + +impl ForwarderServiceType { + // Generate a username using the specified email forwarding service + // This requires an HTTP client to be passed in, as the service will need to make API calls + pub async fn generate( + self, + http: &reqwest::Client, + website: Option, + ) -> Result { + use ForwarderServiceType::*; + + use crate::username_forwarders::*; + + match self { + AddyIo { + api_token, + domain, + base_url, + } => addyio::generate(http, api_token, domain, base_url, website).await, + DuckDuckGo { token } => duckduckgo::generate(http, token).await, + Firefox { api_token } => firefox::generate(http, api_token, website).await, + Fastmail { api_token } => fastmail::generate(http, api_token, website).await, + ForwardEmail { api_token, domain } => { + forwardemail::generate(http, api_token, domain, website).await + } + SimpleLogin { api_key } => simplelogin::generate(http, api_key, website).await, + } + } +} + +/// Implementation of the username generator. +/// +/// 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( + input: UsernameGeneratorRequest, + http: &reqwest::Client, +) -> Result { + use rand::thread_rng; + use UsernameGeneratorRequest::*; + match input { + Word { + capitalize, + include_number, + } => Ok(username_word(&mut thread_rng(), capitalize, include_number)), + Subaddress { r#type, email } => Ok(username_subaddress(&mut thread_rng(), r#type, email)), + Catchall { r#type, domain } => Ok(username_catchall(&mut thread_rng(), r#type, domain)), + Forwarded { service, website } => service.generate(http, website).await, + } +} + +fn username_word(mut rng: impl RngCore, capitalize: bool, include_number: bool) -> String { + let word = EFF_LONG_WORD_LIST + .choose(&mut rng) + .expect("slice is not empty"); + + let mut word = if capitalize { + capitalize_first_letter(word) + } else { + word.to_string() + }; + + if include_number { + word.push_str(&random_number(&mut rng)); + } + + word +} + +/// Generate a random 4 digit number, including leading zeros +fn random_number(mut rng: impl RngCore) -> String { + let num = rng.gen_range(0..=9999); + format!("{num:0>4}") +} + +/// Generate a username using a plus addressed email address +/// The format is +@ +fn username_subaddress(mut rng: impl RngCore, r#type: AppendType, email: String) -> String { + if email.len() < 3 { + return email; + } + + let (email_begin, email_end) = match email.find('@') { + Some(pos) if pos > 0 && pos < email.len() - 1 => { + email.split_once('@').expect("The email contains @") + } + _ => return email, + }; + + let email_middle = match r#type { + AppendType::Random => random_lowercase_string(&mut rng, 8), + AppendType::WebsiteName { website } => website, + }; + + format!("{}+{}@{}", email_begin, email_middle, email_end) +} + +/// Generate a username using a catchall email address +/// The format is @ +fn username_catchall(mut rng: impl RngCore, r#type: AppendType, domain: String) -> String { + if domain.is_empty() { + return domain; + } + + let email_start = match r#type { + AppendType::Random => random_lowercase_string(&mut rng, 8), + AppendType::WebsiteName { website } => website, + }; + + format!("{}@{}", email_start, domain) +} + +fn random_lowercase_string(mut rng: impl RngCore, length: usize) -> String { + const LOWERCASE_ALPHANUMERICAL: &[u8] = b"abcdefghijklmnopqrstuvwxyz1234567890"; + let dist = rand::distributions::Slice::new(LOWERCASE_ALPHANUMERICAL).expect("Non-empty slice"); + + dist.sample_iter(&mut rng) + .take(length) + .map(|&b| b as char) + .collect() +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + + pub use super::*; + + #[test] + fn test_username_word() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + assert_eq!(username_word(&mut rng, true, true), "Subsystem6314"); + assert_eq!(username_word(&mut rng, true, false), "Silenced"); + assert_eq!(username_word(&mut rng, false, true), "dinginess4487"); + } + + #[test] + fn test_username_subaddress() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]); + let user = username_subaddress(&mut rng, AppendType::Random, "demo@test.com".into()); + assert_eq!(user, "demo+5wiejdaj@test.com"); + + let user = username_subaddress( + &mut rng, + AppendType::WebsiteName { + website: "bitwarden.com".into(), + }, + "demo@test.com".into(), + ); + assert_eq!(user, "demo+bitwarden.com@test.com"); + } + + #[test] + fn test_username_catchall() { + let mut rng = rand_chacha::ChaCha8Rng::from_seed([1u8; 32]); + let user = username_catchall(&mut rng, AppendType::Random, "test.com".into()); + assert_eq!(user, "k9y6yw7j@test.com"); + + let user = username_catchall( + &mut rng, + AppendType::WebsiteName { + website: "bitwarden.com".into(), + }, + "test.com".into(), + ); + assert_eq!(user, "bitwarden.com@test.com"); + } +} diff --git a/crates/bitwarden-generators/src/username_forwarders/addyio.rs b/crates/bitwarden-generators/src/username_forwarders/addyio.rs new file mode 100644 index 000000000..4b75e1c84 --- /dev/null +++ b/crates/bitwarden-generators/src/username_forwarders/addyio.rs @@ -0,0 +1,158 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::username::UsernameError; + +pub async fn generate( + http: &reqwest::Client, + api_token: String, + domain: String, + base_url: String, + website: Option, +) -> Result { + let description = super::format_description(&website); + + #[derive(serde::Serialize)] + struct Request { + domain: String, + description: String, + } + + let response = http + .post(format!("{base_url}/api/v1/aliases")) + .header(CONTENT_TYPE, "application/json") + .bearer_auth(api_token) + .header("X-Requested-With", "XMLHttpRequest") + .json(&Request { + domain, + description, + }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err(UsernameError::InvalidApiKey); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct ResponseData { + email: String, + } + #[derive(serde::Deserialize)] + struct Response { + data: ResponseData, + } + let response: Response = response.json().await?; + + Ok(response.data.email) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use crate::username::UsernameError; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + // Mock the request to the addy.io API, and verify that the correct request is made + server + .register( + Mock::given(matchers::path("/api/v1/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .and(matchers::body_json(json!({ + "domain": "myemail.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "data": { + "id": "50c9e585-e7f5-41c4-9016-9014c15454bc", + "user_id": "ca0a4e09-c266-4f6f-845c-958db5090f09", + "local_part": "50c9e585-e7f5-41c4-9016-9014c15454bc", + "domain": "myemail.com", + "email": "50c9e585-e7f5-41c4-9016-9014c15454bc@myemail.com", + "active": true + } + }))) + .expect(1), + ) + .await; + // Mock an invalid API token request + server + .register( + Mock::given(matchers::path("/api/v1/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .and(matchers::body_json(json!({ + "domain": "myemail.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ) + .await; + // Mock an invalid domain + server + .register( + Mock::given(matchers::path("/api/v1/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .and(matchers::body_json(json!({ + "domain": "gmail.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(403)) + .expect(1), + ) + .await; + + let address = super::generate( + &reqwest::Client::new(), + "MY_TOKEN".into(), + "myemail.com".into(), + format!("http://{}", server.address()), + Some("example.com".into()), + ) + .await + .unwrap(); + + let fake_token_error = super::generate( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + "myemail.com".into(), + format!("http://{}", server.address()), + Some("example.com".into()), + ) + .await + .unwrap_err(); + + assert_eq!( + fake_token_error.to_string(), + UsernameError::InvalidApiKey.to_string() + ); + + let fake_domain_error = super::generate( + &reqwest::Client::new(), + "MY_TOKEN".into(), + "gmail.com".into(), + format!("http://{}", server.address()), + Some("example.com".into()), + ) + .await + .unwrap_err(); + + assert!(fake_domain_error.to_string().contains("403 Forbidden")); + + server.verify().await; + assert_eq!(address, "50c9e585-e7f5-41c4-9016-9014c15454bc@myemail.com"); + } +} diff --git a/crates/bitwarden-generators/src/username_forwarders/duckduckgo.rs b/crates/bitwarden-generators/src/username_forwarders/duckduckgo.rs new file mode 100644 index 000000000..3f21fd3a5 --- /dev/null +++ b/crates/bitwarden-generators/src/username_forwarders/duckduckgo.rs @@ -0,0 +1,97 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::username::UsernameError; + +pub async fn generate(http: &reqwest::Client, token: String) -> Result { + generate_with_api_url(http, token, "https://quack.duckduckgo.com".into()).await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + token: String, + api_url: String, +) -> Result { + let response = http + .post(format!("{api_url}/api/email/addresses")) + .header(CONTENT_TYPE, "application/json") + .bearer_auth(token) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err(UsernameError::InvalidApiKey); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct Response { + address: String, + } + let response: Response = response.json().await?; + + Ok(format!("{}@duck.com", response.address)) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use crate::username::UsernameError; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + // Mock the request to the DDG API, and verify that the correct request is made + server + .register( + Mock::given(matchers::path("/api/email/addresses")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "address": "bw7prt" + }))) + .expect(1), + ) + .await; + // Mock an invalid token request + server + .register( + Mock::given(matchers::path("/api/email/addresses")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "bw7prt@duck.com"); + + let fake_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert_eq!( + fake_token_error.to_string(), + UsernameError::InvalidApiKey.to_string() + ); + + server.verify().await; + } +} diff --git a/crates/bitwarden-generators/src/username_forwarders/fastmail.rs b/crates/bitwarden-generators/src/username_forwarders/fastmail.rs new file mode 100644 index 000000000..8a73250c4 --- /dev/null +++ b/crates/bitwarden-generators/src/username_forwarders/fastmail.rs @@ -0,0 +1,206 @@ +use std::collections::HashMap; + +use reqwest::{header::CONTENT_TYPE, StatusCode}; +use serde_json::json; + +use crate::username::UsernameError; + +pub async fn generate( + http: &reqwest::Client, + api_token: String, + website: Option, +) -> Result { + generate_with_api_url(http, api_token, website, "https://api.fastmail.com".into()).await +} + +pub async fn generate_with_api_url( + http: &reqwest::Client, + api_token: String, + website: Option, + api_url: String, +) -> Result { + let account_id = get_account_id(http, &api_token, &api_url).await?; + + let response = http + .post(format!("{api_url}/jmap/api/")) + .header(CONTENT_TYPE, "application/json") + .bearer_auth(api_token) + .json(&json!({ + "using": ["https://www.fastmail.com/dev/maskedemail", "urn:ietf:params:jmap:core"], + "methodCalls": [[ + "MaskedEmail/set", { + "accountId": account_id, + "create": { + "new-masked-email": { + "state": "enabled", + "description": "", + "forDomain": website, + "emailPrefix": null, + }, + }, + }, + "0", + ]], + })) + .send() + .await?; + + let status_code = response.status(); + if status_code == StatusCode::UNAUTHORIZED { + return Err(UsernameError::InvalidApiKey); + } + + let response_json: serde_json::Value = response.json().await?; + let Some(r) = response_json.get("methodResponses").and_then(|r| r.get(0)) else { + return Err(UsernameError::Unknown); + }; + let method_response = r.get(0).and_then(|r| r.as_str()); + let response_value = r.get(1); + + if method_response == Some("MaskedEmail/set") { + if let Some(email) = response_value + .and_then(|r| r.get("created")) + .and_then(|r| r.get("new-masked-email")) + .and_then(|r| r.get("email")) + .and_then(|r| r.as_str()) + { + return Ok(email.to_owned()); + }; + + let error_description = response_value + .and_then(|r| r.get("notCreated")) + .and_then(|r| r.get("new-masked-email")) + .and_then(|r| r.get("description")) + .and_then(|r| r.as_str()) + .unwrap_or("Unknown error"); + + return Err(UsernameError::ResponseContent { + status: status_code, + message: error_description.to_owned(), + }); + } else if method_response == Some("error") { + let error_description = response_value + .and_then(|r| r.get("description")) + .and_then(|r| r.as_str()) + .unwrap_or("Unknown error"); + + return Err(UsernameError::ResponseContent { + status: status_code, + message: error_description.to_owned(), + }); + } + + Err(UsernameError::Unknown) +} + +async fn get_account_id( + client: &reqwest::Client, + api_token: &str, + api_url: &str, +) -> Result { + #[derive(serde::Deserialize)] + struct Response { + #[serde(rename = "primaryAccounts")] + primary_accounts: HashMap, + } + let response = client + .get(format!("{api_url}/.well-known/jmap")) + .bearer_auth(api_token) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err(UsernameError::InvalidApiKey); + } + + response.error_for_status_ref()?; + let mut response: Response = response.json().await?; + + Ok(response + .primary_accounts + .remove("https://www.fastmail.com/dev/maskedemail") + .unwrap_or_default()) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use crate::username::UsernameError; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + // Mock a valid request to FastMail API + server.register(Mock::given(matchers::path("/.well-known/jmap")) + .and(matchers::method("GET")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "primaryAccounts": { + "https://www.fastmail.com/dev/maskedemail": "ca0a4e09-c266-4f6f-845c-958db5090f09" + } + }))) + .expect(1)).await; + + server.register(Mock::given(matchers::path("/jmap/api/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_TOKEN")) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "methodResponses": [ + ["MaskedEmail/set", {"created": {"new-masked-email": {"email": "9f823dq23d123ds@mydomain.com"}}}] + ] + }))) + .expect(1)).await; + + // Mock an invalid token request + server + .register( + Mock::given(matchers::path("/.well-known/jmap")) + .and(matchers::method("GET")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ) + .await; + + server + .register( + Mock::given(matchers::path("/jmap/api/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN")) + .respond_with(ResponseTemplate::new(201)) + .expect(0), + ) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "9f823dq23d123ds@mydomain.com"); + + let fake_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert_eq!( + fake_token_error.to_string(), + UsernameError::InvalidApiKey.to_string() + ); + + server.verify().await; + } +} diff --git a/crates/bitwarden-generators/src/username_forwarders/firefox.rs b/crates/bitwarden-generators/src/username_forwarders/firefox.rs new file mode 100644 index 000000000..66c2a3a2c --- /dev/null +++ b/crates/bitwarden-generators/src/username_forwarders/firefox.rs @@ -0,0 +1,173 @@ +use reqwest::{ + header::{self}, + StatusCode, +}; + +use crate::username::UsernameError; + +pub async fn generate( + http: &reqwest::Client, + api_token: String, + website: Option, +) -> Result { + generate_with_api_url(http, api_token, website, "https://relay.firefox.com".into()).await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + api_token: String, + website: Option, + api_url: String, +) -> Result { + #[derive(serde::Serialize)] + struct Request { + enabled: bool, + #[serde(skip_serializing_if = "Option::is_none")] + generated_for: Option, + description: String, + } + + let description = super::format_description_ff(&website); + + let response = http + .post(format!("{api_url}/api/v1/relayaddresses/")) + .header(header::AUTHORIZATION, format!("Token {api_token}")) + .json(&Request { + enabled: true, + generated_for: website, + description, + }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err(UsernameError::InvalidApiKey); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct Response { + full_address: String, + } + let response: Response = response.json().await?; + + Ok(response.full_address) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use crate::username::UsernameError; + + #[tokio::test] + async fn test_mock_success() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + server + .register( + Mock::given(matchers::path("/api/v1/relayaddresses/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Token MY_TOKEN")) + .and(matchers::body_json(json!({ + "enabled": true, + "generated_for": "example.com", + "description": "example.com - Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "full_address": "ofuj4d4qw@mozmail.com" + }))) + .expect(1), + ) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "ofuj4d4qw@mozmail.com"); + + server.verify().await; + } + + #[tokio::test] + async fn test_mock_without_website() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + server + .register( + Mock::given(matchers::path("/api/v1/relayaddresses/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Token MY_OTHER_TOKEN")) + .and(matchers::body_json(json!({ + "enabled": true, + "description": "Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "full_address": "856f7765@mozmail.com" + }))) + .expect(1), + ) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_OTHER_TOKEN".into(), + None, + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "856f7765@mozmail.com"); + + server.verify().await; + } + + #[tokio::test] + async fn test_mock_invalid_token() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + server + .register( + Mock::given(matchers::path("/api/v1/relayaddresses/")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Token MY_FAKE_TOKEN")) + .and(matchers::body_json(json!({ + "enabled": true, + "generated_for": "example.com", + "description": "example.com - Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ) + .await; + + let error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert_eq!(error.to_string(), UsernameError::InvalidApiKey.to_string()); + + server.verify().await; + } +} diff --git a/crates/bitwarden-generators/src/username_forwarders/forwardemail.rs b/crates/bitwarden-generators/src/username_forwarders/forwardemail.rs new file mode 100644 index 000000000..1cec22882 --- /dev/null +++ b/crates/bitwarden-generators/src/username_forwarders/forwardemail.rs @@ -0,0 +1,209 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::username::UsernameError; + +pub async fn generate( + http: &reqwest::Client, + api_token: String, + domain: String, + website: Option, +) -> Result { + generate_with_api_url( + http, + api_token, + domain, + website, + "https://api.forwardemail.net".into(), + ) + .await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + api_token: String, + domain: String, + website: Option, + api_url: String, +) -> Result { + let description = super::format_description(&website); + + #[derive(serde::Serialize)] + struct Request { + labels: Option, + description: String, + } + + let response = http + .post(format!("{api_url}/v1/domains/{domain}/aliases")) + .header(CONTENT_TYPE, "application/json") + .basic_auth(api_token, None::) + .json(&Request { + description, + labels: website, + }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err(UsernameError::InvalidApiKey); + } + + #[derive(serde::Deserialize)] + struct ResponseDomain { + name: Option, + } + #[derive(serde::Deserialize)] + struct Response { + name: Option, + domain: Option, + + message: Option, + error: Option, + } + let status = response.status(); + let response: Response = response.json().await?; + + if status.is_success() { + if let Some(name) = response.name { + if let Some(response_domain) = response.domain { + return Ok(format!( + "{}@{}", + name, + response_domain.name.unwrap_or(domain) + )); + } + } + } + + if let Some(message) = response.message { + return Err(UsernameError::ResponseContent { status, message }); + } + if let Some(message) = response.error { + return Err(UsernameError::ResponseContent { status, message }); + } + + Err(UsernameError::Unknown) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use crate::username::UsernameError; + + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + // Mock the request to the ForwardEmail API, and verify that the correct request is made + server + .register( + Mock::given(matchers::path("/v1/domains/mydomain.com/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authorization", "Basic TVlfVE9LRU46")) + .and(matchers::body_json(json!({ + "labels": "example.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "name": "wertg8ad", + "domain": { + "name": "mydomain.com" + } + }))) + .expect(1), + ) + .await; + + // Mock an invalid API token request + server + .register( + Mock::given(matchers::path("/v1/domains/mydomain.com/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header( + "Authorization", + "Basic TVlfRkFLRV9UT0tFTjo=", + )) + .and(matchers::body_json(json!({ + "labels": "example.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401).set_body_json(json!({ + "statusCode": 401, + "error": "Unauthorized", + "message": "Invalid API token." + }))) + .expect(1), + ) + .await; + + // Mock a free API token request + server + .register( + Mock::given(matchers::path("/v1/domains/mydomain.com/aliases")) + .and(matchers::method("POST")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header( + "Authorization", + "Basic TVlfRlJFRV9UT0tFTjo=", + )) + .and(matchers::body_json(json!({ + "labels": "example.com", + "description": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(402).set_body_json(json!({ + "statusCode": 402, + "error": "Payment required", + "message": "Please upgrade to a paid plan to unlock this feature." + }))) + .expect(1), + ) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + "mydomain.com".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "wertg8ad@mydomain.com"); + + let invalid_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + "mydomain.com".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert_eq!( + invalid_token_error.to_string(), + UsernameError::InvalidApiKey.to_string() + ); + + let free_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FREE_TOKEN".into(), + "mydomain.com".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert!(free_token_error + .to_string() + .contains("Please upgrade to a paid plan")); + + server.verify().await; + } +} diff --git a/crates/bitwarden-generators/src/username_forwarders/mod.rs b/crates/bitwarden-generators/src/username_forwarders/mod.rs new file mode 100644 index 000000000..8d445bc04 --- /dev/null +++ b/crates/bitwarden-generators/src/username_forwarders/mod.rs @@ -0,0 +1,22 @@ +pub(super) mod addyio; +pub(super) mod duckduckgo; +pub(super) mod fastmail; +pub(super) mod firefox; +pub(super) mod forwardemail; +pub(super) mod simplelogin; + +fn format_description(website: &Option) -> String { + let description = website + .as_ref() + .map(|w| format!("Website: {w}. ")) + .unwrap_or_default(); + format!("{description}Generated by Bitwarden.") +} + +fn format_description_ff(website: &Option) -> String { + let description = website + .as_ref() + .map(|w| format!("{w} - ")) + .unwrap_or_default(); + format!("{description}Generated by Bitwarden.") +} diff --git a/crates/bitwarden-generators/src/username_forwarders/simplelogin.rs b/crates/bitwarden-generators/src/username_forwarders/simplelogin.rs new file mode 100644 index 000000000..fa9342267 --- /dev/null +++ b/crates/bitwarden-generators/src/username_forwarders/simplelogin.rs @@ -0,0 +1,125 @@ +use reqwest::{header::CONTENT_TYPE, StatusCode}; + +use crate::username::UsernameError; + +pub async fn generate( + http: &reqwest::Client, + api_key: String, + website: Option, +) -> Result { + generate_with_api_url(http, api_key, website, "https://app.simplelogin.io".into()).await +} + +async fn generate_with_api_url( + http: &reqwest::Client, + api_key: String, + website: Option, + api_url: String, +) -> Result { + let query = website + .as_ref() + .map(|w| format!("?hostname={}", w)) + .unwrap_or_default(); + + let note = super::format_description(&website); + + #[derive(serde::Serialize)] + struct Request { + note: String, + } + + let response = http + .post(format!("{api_url}/api/alias/random/new{query}")) + .header(CONTENT_TYPE, "application/json") + .header("Authentication", api_key) + .json(&Request { note }) + .send() + .await?; + + if response.status() == StatusCode::UNAUTHORIZED { + return Err(UsernameError::InvalidApiKey); + } + + // Throw any other errors + response.error_for_status_ref()?; + + #[derive(serde::Deserialize)] + struct Response { + alias: String, + } + let response: Response = response.json().await?; + + Ok(response.alias) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use crate::username::UsernameError; + #[tokio::test] + async fn test_mock_server() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + let server = wiremock::MockServer::start().await; + + // Mock the request to the SimpleLogin API, and verify that the correct request is made + server + .register( + Mock::given(matchers::path("/api/alias/random/new")) + .and(matchers::method("POST")) + .and(matchers::query_param("hostname", "example.com")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authentication", "MY_TOKEN")) + .and(matchers::body_json(json!({ + "note": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(201).set_body_json(json!({ + "alias": "simplelogin.yut3g8@aleeas.com", + }))) + .expect(1), + ) + .await; + // Mock an invalid token request + server + .register( + Mock::given(matchers::path("/api/alias/random/new")) + .and(matchers::method("POST")) + .and(matchers::query_param("hostname", "example.com")) + .and(matchers::header("Content-Type", "application/json")) + .and(matchers::header("Authentication", "MY_FAKE_TOKEN")) + .and(matchers::body_json(json!({ + "note": "Website: example.com. Generated by Bitwarden." + }))) + .respond_with(ResponseTemplate::new(401)) + .expect(1), + ) + .await; + + let address = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap(); + assert_eq!(address, "simplelogin.yut3g8@aleeas.com"); + + let fake_token_error = super::generate_with_api_url( + &reqwest::Client::new(), + "MY_FAKE_TOKEN".into(), + Some("example.com".into()), + format!("http://{}", server.address()), + ) + .await + .unwrap_err(); + + assert_eq!( + fake_token_error.to_string(), + UsernameError::InvalidApiKey.to_string() + ); + + server.verify().await; + } +} diff --git a/crates/bitwarden-generators/src/util.rs b/crates/bitwarden-generators/src/util.rs new file mode 100644 index 000000000..e434500ea --- /dev/null +++ b/crates/bitwarden-generators/src/util.rs @@ -0,0 +1,10 @@ +pub(crate) fn capitalize_first_letter(s: &str) -> String { + // Unicode case conversion can change the length of the string, so we can't capitalize in place. + // Instead we extract the first character and convert it to uppercase. This returns + // an iterator which we collect into a string, and then append the rest of the input. + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } +} diff --git a/crates/bitwarden-generators/uniffi.toml b/crates/bitwarden-generators/uniffi.toml new file mode 100644 index 000000000..75f929b1a --- /dev/null +++ b/crates/bitwarden-generators/uniffi.toml @@ -0,0 +1,9 @@ +[bindings.kotlin] +package_name = "com.bitwarden.generators" +generate_immutable_records = true +android = true + +[bindings.swift] +ffi_module_name = "BitwardenGeneratorsFFI" +module_name = "BitwardenGenerators" +generate_immutable_records = true diff --git a/crates/bitwarden-json/Cargo.toml b/crates/bitwarden-json/Cargo.toml index 579a8bdab..4981e8f4b 100644 --- a/crates/bitwarden-json/Cargo.toml +++ b/crates/bitwarden-json/Cargo.toml @@ -1,26 +1,31 @@ [package] name = "bitwarden-json" version = "0.3.0" -authors = ["Bitwarden Inc"] -license-file = "LICENSE" -repository = "https://github.com/bitwarden/sdk" -homepage = "https://bitwarden.com" description = """ JSON bindings for the Bitwarden Secret Manager SDK """ -keywords = ["bitwarden", "secrets manager"] +keywords = ["bitwarden", "secrets-manager"] categories = ["api-bindings"] -edition = "2021" -rust-version = "1.57" +publish = false + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true [features] internal = ["bitwarden/internal"] # Internal testing methods 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" -log = ">=0.4.18, <0.5" -bitwarden = { path = "../bitwarden" } +[lints] +workspace = true diff --git a/crates/bitwarden-json/LICENSE b/crates/bitwarden-json/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden-json/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs index f771a5ce4..ef9414f12 100644 --- a/crates/bitwarden-json/src/client.rs +++ b/crates/bitwarden-json/src/client.rs @@ -1,3 +1,4 @@ +use async_lock::Mutex; use bitwarden::client::client_settings::ClientSettings; #[cfg(feature = "secrets")] @@ -7,15 +8,15 @@ use crate::{ response::{Response, ResponseIntoString}, }; -pub struct Client(bitwarden::Client); +pub struct Client(Mutex); impl Client { pub fn new(settings_input: Option) -> Self { let settings = Self::parse_settings(settings_input); - Self(bitwarden::Client::new(settings)) + Self(Mutex::new(bitwarden::Client::new(settings))) } - pub async fn run_command(&mut self, input_str: &str) -> String { + pub async fn run_command(&self, input_str: &str) -> String { const SUBCOMMANDS_TO_CLEAN: &[&str] = &["Secrets"]; let mut cmd_value: serde_json::Value = match serde_json::from_str(input_str) { Ok(cmd) => cmd, @@ -44,39 +45,43 @@ impl Client { } }; + let mut client = self.0.lock().await; + match cmd { #[cfg(feature = "internal")] - Command::PasswordLogin(req) => self.0.password_login(&req).await.into_string(), + Command::PasswordLogin(req) => client.auth().login_password(&req).await.into_string(), #[cfg(feature = "secrets")] - Command::AccessTokenLogin(req) => self.0.access_token_login(&req).await.into_string(), + Command::AccessTokenLogin(req) => { + client.auth().login_access_token(&req).await.into_string() + } #[cfg(feature = "internal")] - Command::GetUserApiKey(req) => self.0.get_user_api_key(&req).await.into_string(), + Command::GetUserApiKey(req) => client.get_user_api_key(&req).await.into_string(), #[cfg(feature = "internal")] - Command::ApiKeyLogin(req) => self.0.api_key_login(&req).await.into_string(), + Command::ApiKeyLogin(req) => client.auth().login_api_key(&req).await.into_string(), #[cfg(feature = "internal")] - Command::Sync(req) => self.0.sync(&req).await.into_string(), + Command::Sync(req) => client.sync(&req).await.into_string(), #[cfg(feature = "internal")] - Command::Fingerprint(req) => self.0.fingerprint(&req).into_string(), + Command::Fingerprint(req) => client.platform().fingerprint(&req).into_string(), #[cfg(feature = "secrets")] Command::Secrets(cmd) => match cmd { - SecretsCommand::Get(req) => self.0.secrets().get(&req).await.into_string(), + SecretsCommand::Get(req) => client.secrets().get(&req).await.into_string(), SecretsCommand::GetByIds(req) => { - self.0.secrets().get_by_ids(req).await.into_string() + client.secrets().get_by_ids(req).await.into_string() } - SecretsCommand::Create(req) => self.0.secrets().create(&req).await.into_string(), - SecretsCommand::List(req) => self.0.secrets().list(&req).await.into_string(), - SecretsCommand::Update(req) => self.0.secrets().update(&req).await.into_string(), - SecretsCommand::Delete(req) => self.0.secrets().delete(req).await.into_string(), + SecretsCommand::Create(req) => client.secrets().create(&req).await.into_string(), + SecretsCommand::List(req) => client.secrets().list(&req).await.into_string(), + SecretsCommand::Update(req) => client.secrets().update(&req).await.into_string(), + SecretsCommand::Delete(req) => client.secrets().delete(req).await.into_string(), }, #[cfg(feature = "secrets")] Command::Projects(cmd) => match cmd { - ProjectsCommand::Get(req) => self.0.projects().get(&req).await.into_string(), - ProjectsCommand::Create(req) => self.0.projects().create(&req).await.into_string(), - ProjectsCommand::List(req) => self.0.projects().list(&req).await.into_string(), - ProjectsCommand::Update(req) => self.0.projects().update(&req).await.into_string(), - ProjectsCommand::Delete(req) => self.0.projects().delete(req).await.into_string(), + ProjectsCommand::Get(req) => client.projects().get(&req).await.into_string(), + ProjectsCommand::Create(req) => client.projects().create(&req).await.into_string(), + ProjectsCommand::List(req) => client.projects().list(&req).await.into_string(), + ProjectsCommand::Update(req) => client.projects().update(&req).await.into_string(), + ProjectsCommand::Delete(req) => client.projects().delete(req).await.into_string(), }, } } diff --git a/crates/bitwarden-json/src/command.rs b/crates/bitwarden-json/src/command.rs index 855a30399..8da5e8cbd 100644 --- a/crates/bitwarden-json/src/command.rs +++ b/crates/bitwarden-json/src/command.rs @@ -33,7 +33,6 @@ pub enum Command { /// This command is not capable of handling authentication requiring 2fa or captcha. /// /// Returns: [PasswordLoginResponse](bitwarden::auth::login::PasswordLoginResponse) - /// PasswordLogin(PasswordLoginRequest), #[cfg(feature = "internal")] @@ -42,7 +41,6 @@ pub enum Command { /// This command is for initiating an authentication handshake with Bitwarden. /// /// Returns: [ApiKeyLoginResponse](bitwarden::auth::login::ApiKeyLoginResponse) - /// ApiKeyLogin(ApiKeyLoginRequest), #[cfg(feature = "secrets")] @@ -51,7 +49,6 @@ pub enum Command { /// This command is for initiating an authentication handshake with Bitwarden. /// /// Returns: [ApiKeyLoginResponse](bitwarden::auth::login::ApiKeyLoginResponse) - /// AccessTokenLogin(AccessTokenLoginRequest), #[cfg(feature = "internal")] @@ -59,14 +56,12 @@ pub enum Command { /// 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")] @@ -74,7 +69,6 @@ pub enum Command { /// Retrieve all user data, ciphers and organizations the user is a part of /// /// Returns: [SyncResponse](bitwarden::platform::SyncResponse) - /// Sync(SyncRequest), #[cfg(feature = "secrets")] @@ -92,7 +86,6 @@ pub enum SecretsCommand { /// Retrieve a secret by the provided identifier /// /// Returns: [SecretResponse](bitwarden::secrets_manager::secrets::SecretResponse) - /// Get(SecretGetRequest), /// > Requires Authentication @@ -100,7 +93,6 @@ pub enum SecretsCommand { /// Retrieve secrets by the provided identifiers /// /// Returns: [SecretsResponse](bitwarden::secrets_manager::secrets::SecretsResponse) - /// GetByIds(SecretsGetRequest), /// > Requires Authentication @@ -108,15 +100,14 @@ pub enum SecretsCommand { /// Creates a new secret in the provided organization using the given data /// /// Returns: [SecretResponse](bitwarden::secrets_manager::secrets::SecretResponse) - /// Create(SecretCreateRequest), /// > Requires Authentication /// > Requires using an Access Token for login or calling Sync at least once - /// Lists all secret identifiers of the given organization, to then retrieve each secret, use `CreateSecret` + /// Lists all secret identifiers of the given organization, to then retrieve each secret, use + /// `CreateSecret` /// /// Returns: [SecretIdentifiersResponse](bitwarden::secrets_manager::secrets::SecretIdentifiersResponse) - /// List(SecretIdentifiersRequest), /// > Requires Authentication @@ -124,7 +115,6 @@ pub enum SecretsCommand { /// Updates an existing secret with the provided ID using the given data /// /// Returns: [SecretResponse](bitwarden::secrets_manager::secrets::SecretResponse) - /// Update(SecretPutRequest), /// > Requires Authentication @@ -132,7 +122,6 @@ pub enum SecretsCommand { /// Deletes all the secrets whose IDs match the provided ones /// /// Returns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse) - /// Delete(SecretsDeleteRequest), } @@ -145,7 +134,6 @@ pub enum ProjectsCommand { /// Retrieve a project by the provided identifier /// /// Returns: [ProjectResponse](bitwarden::secrets_manager::projects::ProjectResponse) - /// Get(ProjectGetRequest), /// > Requires Authentication @@ -153,7 +141,6 @@ pub enum ProjectsCommand { /// Creates a new project in the provided organization using the given data /// /// Returns: [ProjectResponse](bitwarden::secrets_manager::projects::ProjectResponse) - /// Create(ProjectCreateRequest), /// > Requires Authentication @@ -161,7 +148,6 @@ pub enum ProjectsCommand { /// Lists all projects of the given organization /// /// Returns: [ProjectsResponse](bitwarden::secrets_manager::projects::ProjectsResponse) - /// List(ProjectsListRequest), /// > Requires Authentication @@ -169,7 +155,6 @@ pub enum ProjectsCommand { /// Updates an existing project with the provided ID using the given data /// /// Returns: [ProjectResponse](bitwarden::secrets_manager::projects::ProjectResponse) - /// Update(ProjectPutRequest), /// > Requires Authentication @@ -177,6 +162,5 @@ pub enum ProjectsCommand { /// Deletes all the projects whose IDs match the provided ones /// /// Returns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse) - /// Delete(ProjectsDeleteRequest), } diff --git a/crates/bitwarden-json/src/response.rs b/crates/bitwarden-json/src/response.rs index 25c31ef75..b76d97aa6 100644 --- a/crates/bitwarden-json/src/response.rs +++ b/crates/bitwarden-json/src/response.rs @@ -57,7 +57,7 @@ impl ResponseIntoString for Response { Ok(ser) => ser, Err(e) => { let error = Response::error(format!("Failed to serialize Response: {}", e)); - serde_json::to_string(&error).unwrap() + serde_json::to_string(&error).expect("Serialize should be infallible") } } } diff --git a/crates/bitwarden-napi/Cargo.toml b/crates/bitwarden-napi/Cargo.toml index bc170631d..16853dbbf 100644 --- a/crates/bitwarden-napi/Cargo.toml +++ b/crates/bitwarden-napi/Cargo.toml @@ -1,30 +1,36 @@ [package] name = "bitwarden-napi" -version = "0.3.0" -authors = ["Bitwarden Inc"] -license-file = "LICENSE" -repository = "https://github.com/bitwarden/sdk" -homepage = "https://bitwarden.com" +version = "0.3.1" description = """ N-API bindings for the Bitwarden Secrets Manager SDK """ -keywords = ["bitwarden", "secrets manager"] -edition = "2021" -rust-version = "1.57" +keywords = ["bitwarden", "secrets-manager"] +publish = false + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true [lib] crate-type = ["cdylib", "rlib"] [dependencies] -napi = {version="2", features=["async"]} +bitwarden-json = { path = "../bitwarden-json", version = "0.3.0", features = [ + "secrets", +] } +env_logger = "0.11.1" +log = "0.4.20" +napi = { version = "2", features = ["async"] } napi-derive = "2" -log = "0.4.18" -env_logger="0.10.0" - -bitwarden-json = { path = "../bitwarden-json", version = "0.3.0", features = ["secrets"] } [build-dependencies] -napi-build = "2.0.1" +napi-build = "2.1.0" [profile.release] lto = true + +[lints] +workspace = true diff --git a/crates/bitwarden-napi/LICENSE b/crates/bitwarden-napi/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden-napi/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden-napi/README.md b/crates/bitwarden-napi/README.md index d16c8152d..d9e3e7f27 100644 --- a/crates/bitwarden-napi/README.md +++ b/crates/bitwarden-napi/README.md @@ -20,7 +20,7 @@ const accessToken = "-- REDACTED --"; const client = new BitwardenClient(settings, LogLevel.Info); -// Authenticating using a service accounts access token +// Authenticating using a machine account access token const result = await client.loginWithAccessToken(accessToken); if (!result.success) { throw Error("Authentication failed"); diff --git a/crates/bitwarden-napi/npm/darwin-arm64/package.json b/crates/bitwarden-napi/npm/darwin-arm64/package.json index b9c1c95b7..52d785d87 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.0", + "version": "0.3.1", "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 66f352df4..0a1b06423 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.0", + "version": "0.3.1", "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 7f91c241c..2284d8415 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.0", + "version": "0.3.1", "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 e4cfa367c..261554e93 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.0", + "version": "0.3.1", "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 02ea37bec..c1c3f1a86 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -1,16 +1,16 @@ { "name": "@bitwarden/sdk-napi", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/sdk-napi", - "version": "0.3.0", + "version": "0.3.1", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@napi-rs/cli": "^2.13.2", - "ts-node": "10.9.1", + "ts-node": "10.9.2", "typescript": "^5.0.0" }, "engines": { @@ -30,9 +30,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "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" @@ -55,9 +55,9 @@ } }, "node_modules/@napi-rs/cli": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.3.tgz", - "integrity": "sha512-3mLNPlbbOhpbIUKicLrJtIearlHXUuXL3UeueYyRRplpVMNkdn8xCyzY6PcYZi3JXR8bmCOiWgkVmLnrSL7DKw==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz", + "integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==", "dev": true, "bin": { "napi": "scripts/index.js" @@ -95,16 +95,19 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.6.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.5.tgz", - "integrity": "sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dev": true, - "peer": true + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "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" @@ -114,9 +117,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "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" @@ -150,9 +153,9 @@ "dev": true }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -193,9 +196,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -205,6 +208,13 @@ "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", diff --git a/crates/bitwarden-napi/package.json b/crates/bitwarden-napi/package.json index 50bebb58b..509cf1763 100644 --- a/crates/bitwarden-napi/package.json +++ b/crates/bitwarden-napi/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/sdk-napi", - "version": "0.3.0", + "version": "0.3.1", "homepage": "https://github.com/bitwarden/sdk#readme", "bugs": { "url": "https://github.com/bitwarden/sdk/issues" @@ -30,7 +30,7 @@ }, "devDependencies": { "@napi-rs/cli": "^2.13.2", - "ts-node": "10.9.1", + "ts-node": "10.9.2", "typescript": "^5.0.0" }, "engines": { diff --git a/crates/bitwarden-napi/src/client.rs b/crates/bitwarden-napi/src/client.rs index 840fd2533..f41d5c351 100644 --- a/crates/bitwarden-napi/src/client.rs +++ b/crates/bitwarden-napi/src/client.rs @@ -1,7 +1,6 @@ extern crate log; use bitwarden_json::client::Client as JsonClient; -use napi::bindgen_prelude::*; use napi_derive::napi; #[napi] @@ -30,7 +29,8 @@ pub struct BitwardenClient(JsonClient); impl BitwardenClient { #[napi(constructor)] pub fn new(settings_input: Option, log_level: Option) -> Self { - // This will only fail if another logger was already initialized, so we can ignore the result + // This will only fail if another logger was already initialized, so we can ignore the + // result let _ = env_logger::Builder::from_default_env() .filter_level(convert_level(log_level.unwrap_or(LogLevel::Info))) .try_init(); @@ -38,7 +38,7 @@ impl BitwardenClient { } #[napi] - pub async unsafe fn run_command(&mut self, command_input: String) -> String { + pub async fn run_command(&self, command_input: String) -> String { self.0.run_command(&command_input).await } } diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml index 5d8d95575..e9073d802 100644 --- a/crates/bitwarden-py/Cargo.toml +++ b/crates/bitwarden-py/Cargo.toml @@ -1,26 +1,34 @@ [package] name = "bitwarden-py" version = "0.1.0" -edition = "2021" -rust-version = "1.57" +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 -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "bitwarden_py" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.19.1", features = ["extension-module"] } -pyo3-log = "0.8.3" - bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } +pyo3 = { version = "0.20.2", features = ["extension-module"] } +pyo3-log = "0.9.0" [build-dependencies] -pyo3-build-config = { version = "0.19.1" } +pyo3-build-config = { version = "0.20.2" } [target.'cfg(not(target_arch="wasm32"))'.dependencies] -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -pyo3-asyncio = { version = "0.19.0", features = [ +tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } +pyo3-asyncio = { version = "0.20.0", features = [ "attributes", "tokio-runtime", ] } + +[lints] +workspace = true diff --git a/crates/bitwarden-py/LICENSE b/crates/bitwarden-py/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden-py/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden-py/src/client.rs b/crates/bitwarden-py/src/client.rs index 1cfd078bb..c3ea62444 100644 --- a/crates/bitwarden-py/src/client.rs +++ b/crates/bitwarden-py/src/client.rs @@ -8,17 +8,20 @@ pub struct BitwardenClient(JsonClient); impl BitwardenClient { #[new] pub fn new(settings_string: Option) -> Self { - pyo3_log::init(); + // 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)) } #[pyo3(text_signature = "($self, command_input)")] - fn run_command(&mut self, command_input: String) -> String { - run_command(&mut self.0, &command_input) + fn run_command(&self, command_input: String) -> String { + run_command(&self.0, &command_input) } } #[tokio::main] -async fn run_command(client: &mut JsonClient, input_str: &str) -> String { +async fn run_command(client: &JsonClient, input_str: &str) -> String { client.run_command(input_str).await } diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 4c1ce2101..90a106bce 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -1,8 +1,14 @@ [package] name = "bitwarden-uniffi" version = "0.1.0" -edition = "2021" -rust-version = "1.57" +publish = false + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true [features] docs = ["dep:schemars"] # Docs @@ -12,15 +18,21 @@ crate-type = ["lib", "staticlib", "cdylib"] bench = false [dependencies] -async-lock = "2.7.0" -env_logger = "0.10.0" -uniffi = "=0.24.1" +async-lock = "3.3.0" +bitwarden = { workspace = true, features = ["mobile", "internal"] } +bitwarden-crypto = { workspace = true, features = ["mobile"] } +bitwarden-generators = { workspace = true, features = ["mobile"] } +chrono = { version = ">=0.4.26, <0.5", features = [ + "serde", + "std", +], default-features = false } +env_logger = "0.11.1" schemars = { version = ">=0.8, <0.9", optional = true } - -bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } +uniffi = "=0.26.1" +uuid = ">=1.3.3, <2" [build-dependencies] -uniffi = { version = "=0.24.1", features = ["build"] } +uniffi = { version = "=0.26.1", features = ["build"] } -[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] -openssl = { version = "0.10", features = ["vendored"] } +[lints] +workspace = true diff --git a/crates/bitwarden-uniffi/README.md b/crates/bitwarden-uniffi/README.md index 7dff0b291..4b2e61714 100644 --- a/crates/bitwarden-uniffi/README.md +++ b/crates/bitwarden-uniffi/README.md @@ -2,9 +2,12 @@ ## 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 > languages/kotlin/doc.md +npx ts-node ./support/docs/docs.ts > doc.md ``` diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index a5107768e..c6aed44eb 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -1,16 +1,17 @@ use std::sync::Arc; -use bitwarden::{ - auth::{password::MasterPasswordPolicyOptions, RegisterKeyResponse}, - client::kdf::Kdf, +use bitwarden::auth::{ + password::MasterPasswordPolicyOptions, AuthRequestResponse, RegisterKeyResponse, + RegisterTdeKeyResponse, }; +use bitwarden_crypto::{AsymmetricEncString, HashPurpose, Kdf, TrustDeviceResponse}; use crate::{error::Result, Client}; #[derive(uniffi::Object)] pub struct ClientAuth(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientAuth { /// **API Draft:** Calculate Password Strength pub async fn password_strength( @@ -28,7 +29,7 @@ impl ClientAuth { .await } - /// **API Draft:** Evaluate if the provided password satisfies the provided policy + /// Evaluate if the provided password satisfies the provided policy pub async fn satisfies_policy( &self, password: String, @@ -50,6 +51,7 @@ impl ClientAuth { email: String, password: String, kdf_params: Kdf, + purpose: HashPurpose, ) -> Result { Ok(self .0 @@ -57,7 +59,7 @@ impl ClientAuth { .read() .await .kdf() - .hash_password(email, password, kdf_params) + .hash_password(email, password, kdf_params, purpose) .await?) } @@ -76,4 +78,74 @@ impl ClientAuth { .auth() .make_register_keys(email, password, kdf)?) } + + /// Generate keys needed for TDE process + pub async 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, + )?) + } + + /// Validate the user password + /// + /// To retrieve the user's password hash, use [`ClientAuth::hash_password`] with + /// `HashPurpose::LocalAuthentication` during login and persist it. If the login method has no + /// password, use the email OTP. + pub async fn validate_password(&self, password: String, password_hash: String) -> Result { + Ok(self + .0 + .0 + .write() + .await + .auth() + .validate_password(password, password_hash.to_string())?) + } + + /// Validate the user password without knowing the password hash + /// + /// Used for accounts that we know have master passwords but that have not logged in with a + /// 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( + &self, + password: String, + encrypted_user_key: String, + ) -> Result { + Ok(self + .0 + .0 + .write() + .await + .auth() + .validate_password_user_key(password, encrypted_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)?) + } + + /// 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)?) + } + + /// Trust the current device + pub async fn trust_device(&self) -> Result { + Ok(self.0 .0.write().await.auth().trust_device()?) + } } diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs new file mode 100644 index 000000000..f3abe694a --- /dev/null +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -0,0 +1,99 @@ +use std::sync::Arc; + +use bitwarden::mobile::crypto::{ + DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse, +}; +use bitwarden_crypto::{AsymmetricEncString, EncString, SensitiveString}; + +use crate::{error::Result, Client}; + +#[derive(uniffi::Object)] +pub struct ClientCrypto(pub(crate) Arc); + +#[uniffi::export(async_runtime = "tokio")] +impl ClientCrypto { + /// Initialization method for the user crypto. Needs to be called before any other crypto + /// operations. + pub async fn initialize_user_crypto(&self, req: InitUserCryptoRequest) -> Result<()> { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .initialize_user_crypto(req) + .await?) + } + + /// Initialization method for the organization crypto. Needs to be called after + /// `initialize_user_crypto` but before any other crypto operations. + pub async fn initialize_org_crypto(&self, req: InitOrgCryptoRequest) -> Result<()> { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .initialize_org_crypto(req) + .await?) + } + + /// 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?) + } + + /// 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?) + } + + /// 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?) + } + + /// 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 async fn enroll_admin_password_reset( + &self, + public_key: String, + ) -> Result { + Ok(self + .0 + .0 + .write() + .await + .crypto() + .enroll_admin_password_reset(public_key)?) + } +} diff --git a/crates/bitwarden-uniffi/src/docs.rs b/crates/bitwarden-uniffi/src/docs.rs index bd30e974e..55d81beec 100644 --- a/crates/bitwarden-uniffi/src/docs.rs +++ b/crates/bitwarden-uniffi/src/docs.rs @@ -1,10 +1,15 @@ use bitwarden::{ auth::password::MasterPasswordPolicyOptions, - client::kdf::Kdf, - mobile::crypto::InitCryptoRequest, - tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest}, - vault::{Cipher, CipherView, Collection, Folder, FolderView, Send, SendListView, SendView}, + generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest}, + mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, + platform::FingerprintRequest, + tool::ExportFormat, + vault::{ + Cipher, CipherView, Collection, Folder, FolderView, Send, SendListView, SendView, + TotpResponse, + }, }; +use bitwarden_crypto::{HashPurpose, Kdf}; use schemars::JsonSchema; #[derive(JsonSchema)] @@ -21,7 +26,9 @@ pub enum DocRef { SendListView(SendListView), // Crypto - InitCryptoRequest(InitCryptoRequest), + InitUserCryptoRequest(InitUserCryptoRequest), + InitOrgCryptoRequest(InitOrgCryptoRequest), + HashPurpose(HashPurpose), // Generators PasswordGeneratorRequest(PasswordGeneratorRequest), @@ -30,9 +37,15 @@ pub enum DocRef { // 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 1a1e9d29a..5eef9bbd5 100644 --- a/crates/bitwarden-uniffi/src/error.rs +++ b/crates/bitwarden-uniffi/src/error.rs @@ -1,7 +1,8 @@ use std::fmt::{Display, Formatter}; -// Name is converted from *Error to *Exception, so we can't just name the enum Error because Exception already exists -#[derive(uniffi::Error)] +// 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), @@ -21,4 +22,12 @@ impl Display for BitwardenError { } } +impl std::error::Error for BitwardenError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + BitwardenError::E(e) => Some(e), + } + } +} + pub type Result = std::result::Result; diff --git a/crates/bitwarden-uniffi/src/lib.rs b/crates/bitwarden-uniffi/src/lib.rs index 2e2543ab3..b29441fdd 100644 --- a/crates/bitwarden-uniffi/src/lib.rs +++ b/crates/bitwarden-uniffi/src/lib.rs @@ -4,26 +4,28 @@ use std::sync::Arc; use async_lock::RwLock; use auth::ClientAuth; -use bitwarden::{client::client_settings::ClientSettings, mobile::crypto::InitCryptoRequest}; +use bitwarden::client::client_settings::ClientSettings; pub mod auth; +pub mod crypto; mod error; +pub mod platform; pub mod tool; +mod uniffi_support; pub mod vault; #[cfg(feature = "docs")] pub mod docs; +use crypto::ClientCrypto; use error::Result; -use tool::ClientGenerators; +use platform::ClientPlatform; +use tool::{ClientExporters, ClientGenerators}; use vault::ClientVault; #[derive(uniffi::Object)] pub struct Client(RwLock); -#[derive(uniffi::Object)] -pub struct ClientCrypto(Arc); - #[uniffi::export] impl Client { /// Initialize a new instance of the SDK client @@ -42,11 +44,20 @@ impl Client { Arc::new(ClientVault(self)) } + pub fn platform(self: Arc) -> Arc { + Arc::new(ClientPlatform(self)) + } + /// Generator operations pub fn generators(self: Arc) -> Arc { Arc::new(ClientGenerators(self)) } + /// Exporters + pub fn exporters(self: Arc) -> Arc { + Arc::new(ClientExporters(self)) + } + /// Auth operations pub fn auth(self: Arc) -> Arc { Arc::new(ClientAuth(self)) @@ -57,18 +68,3 @@ impl Client { msg } } - -#[uniffi::export] -impl ClientCrypto { - /// Initialization method for the crypto. Needs to be called before any other crypto operations. - pub async fn initialize_crypto(&self, req: InitCryptoRequest) -> Result<()> { - Ok(self - .0 - .0 - .write() - .await - .crypto() - .initialize_crypto(req) - .await?) - } -} diff --git a/crates/bitwarden-uniffi/src/platform/mod.rs b/crates/bitwarden-uniffi/src/platform/mod.rs new file mode 100644 index 000000000..33b14d345 --- /dev/null +++ b/crates/bitwarden-uniffi/src/platform/mod.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use bitwarden::platform::FingerprintRequest; + +use crate::{error::Result, Client}; + +#[derive(uniffi::Object)] +pub struct ClientPlatform(pub(crate) Arc); + +#[uniffi::export(async_runtime = "tokio")] +impl ClientPlatform { + /// Fingerprint (public key) + pub async fn fingerprint(&self, req: FingerprintRequest) -> Result { + Ok(self + .0 + .0 + .write() + .await + .platform() + .fingerprint(&req)? + .fingerprint) + } + + /// Fingerprint using logged in user's public key + pub async fn user_fingerprint(&self, fingerprint_material: String) -> Result { + Ok(self + .0 + .0 + .write() + .await + .platform() + .user_fingerprint(fingerprint_material)?) + } + + /// 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); + Ok(()) + } +} diff --git a/crates/bitwarden-uniffi/src/sdk.udl b/crates/bitwarden-uniffi/src/sdk.udl deleted file mode 100644 index 96cfb31c0..000000000 --- a/crates/bitwarden-uniffi/src/sdk.udl +++ /dev/null @@ -1,8 +0,0 @@ -interface Client { - constructor(optional string settings = ""); - string run_command(string command); -}; - -namespace bitwarden { - -}; diff --git a/crates/bitwarden-uniffi/src/tool/mod.rs b/crates/bitwarden-uniffi/src/tool/mod.rs index 3243cceb6..4a4ea2401 100644 --- a/crates/bitwarden-uniffi/src/tool/mod.rs +++ b/crates/bitwarden-uniffi/src/tool/mod.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use bitwarden::{ - tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest}, + generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest}, + tool::ExportFormat, vault::{Cipher, Collection, Folder}, }; @@ -10,7 +11,7 @@ use crate::{error::Result, Client}; #[derive(uniffi::Object)] pub struct ClientGenerators(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientGenerators { /// **API Draft:** Generate Password pub async fn password(&self, settings: PasswordGeneratorRequest) -> Result { @@ -35,12 +36,24 @@ impl ClientGenerators { .passphrase(settings) .await?) } + + /// **API Draft:** Generate Username + pub async fn username(&self, settings: UsernameGeneratorRequest) -> Result { + Ok(self + .0 + .0 + .read() + .await + .generator() + .username(settings) + .await?) + } } #[derive(uniffi::Object)] pub struct ClientExporters(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientExporters { /// **API Draft:** Export user vault pub async fn export_vault( diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs new file mode 100644 index 000000000..f487dfaa0 --- /dev/null +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -0,0 +1,10 @@ +use bitwarden_crypto::{AsymmetricEncString, EncString, SensitiveString}; +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!(SensitiveString, bitwarden::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(Uuid, bitwarden::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden-uniffi/src/vault/attachments.rs b/crates/bitwarden-uniffi/src/vault/attachments.rs new file mode 100644 index 000000000..0c099b779 --- /dev/null +++ b/crates/bitwarden-uniffi/src/vault/attachments.rs @@ -0,0 +1,94 @@ +use std::{path::Path, sync::Arc}; + +use bitwarden::vault::{Attachment, AttachmentEncryptResult, AttachmentView, Cipher}; + +use crate::{Client, Result}; + +#[derive(uniffi::Object)] +pub struct ClientAttachments(pub Arc); + +#[uniffi::export(async_runtime = "tokio")] +impl ClientAttachments { + /// Encrypt an attachment file in memory + pub async fn encrypt_buffer( + &self, + cipher: Cipher, + attachment: AttachmentView, + buffer: Vec, + ) -> Result { + Ok(self + .0 + .0 + .read() + .await + .vault() + .attachments() + .encrypt_buffer(cipher, attachment, &buffer) + .await?) + } + + /// Encrypt an attachment file located in the file system + pub async fn encrypt_file( + &self, + cipher: Cipher, + attachment: AttachmentView, + decrypted_file_path: String, + encrypted_file_path: String, + ) -> Result { + Ok(self + .0 + .0 + .read() + .await + .vault() + .attachments() + .encrypt_file( + cipher, + attachment, + Path::new(&decrypted_file_path), + Path::new(&encrypted_file_path), + ) + .await?) + } + /// Decrypt an attachment file in memory + pub async fn decrypt_buffer( + &self, + cipher: Cipher, + attachment: Attachment, + buffer: Vec, + ) -> Result> { + Ok(self + .0 + .0 + .read() + .await + .vault() + .attachments() + .decrypt_buffer(cipher, attachment, &buffer) + .await?) + } + + /// Decrypt an attachment file located in the file system + pub async fn decrypt_file( + &self, + cipher: Cipher, + attachment: Attachment, + encrypted_file_path: String, + decrypted_file_path: String, + ) -> Result<()> { + Ok(self + .0 + .0 + .read() + .await + .vault() + .attachments() + .decrypt_file( + cipher, + attachment, + Path::new(&encrypted_file_path), + Path::new(&decrypted_file_path), + ) + .await?) + } +} diff --git a/crates/bitwarden-uniffi/src/vault/ciphers.rs b/crates/bitwarden-uniffi/src/vault/ciphers.rs index 3122fe2c1..dcf224a38 100644 --- a/crates/bitwarden-uniffi/src/vault/ciphers.rs +++ b/crates/bitwarden-uniffi/src/vault/ciphers.rs @@ -1,13 +1,14 @@ use std::sync::Arc; use bitwarden::vault::{Cipher, CipherListView, CipherView}; +use uuid::Uuid; use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientCiphers(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientCiphers { /// Encrypt cipher pub async fn encrypt(&self, cipher_view: CipherView) -> Result { @@ -47,4 +48,21 @@ impl ClientCiphers { .decrypt_list(ciphers) .await?) } + + /// Move a cipher to an organization, reencrypting the cipher key if necessary + pub async fn move_to_organization( + &self, + cipher: CipherView, + organization_id: Uuid, + ) -> Result { + Ok(self + .0 + .0 + .read() + .await + .vault() + .ciphers() + .move_to_organization(cipher, organization_id) + .await?) + } } diff --git a/crates/bitwarden-uniffi/src/vault/collections.rs b/crates/bitwarden-uniffi/src/vault/collections.rs index 0a1ab8976..53a7500ec 100644 --- a/crates/bitwarden-uniffi/src/vault/collections.rs +++ b/crates/bitwarden-uniffi/src/vault/collections.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientCollections(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientCollections { /// Decrypt collection pub async fn decrypt(&self, collection: Collection) -> Result { diff --git a/crates/bitwarden-uniffi/src/vault/folders.rs b/crates/bitwarden-uniffi/src/vault/folders.rs index d9f955b97..7cbbdc0ae 100644 --- a/crates/bitwarden-uniffi/src/vault/folders.rs +++ b/crates/bitwarden-uniffi/src/vault/folders.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientFolders(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientFolders { /// Encrypt folder pub async fn encrypt(&self, folder: FolderView) -> Result { diff --git a/crates/bitwarden-uniffi/src/vault/mod.rs b/crates/bitwarden-uniffi/src/vault/mod.rs index 2692632ab..2205e0673 100644 --- a/crates/bitwarden-uniffi/src/vault/mod.rs +++ b/crates/bitwarden-uniffi/src/vault/mod.rs @@ -1,7 +1,11 @@ use std::sync::Arc; -use crate::Client; +use bitwarden::vault::TotpResponse; +use chrono::{DateTime, Utc}; +use crate::{error::Result, Client}; + +pub mod attachments; pub mod ciphers; pub mod collections; pub mod folders; @@ -11,7 +15,7 @@ pub mod sends; #[derive(uniffi::Object)] pub struct ClientVault(pub(crate) Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientVault { /// Folder operations pub fn folders(self: Arc) -> Arc { @@ -37,4 +41,23 @@ impl ClientVault { pub fn sends(self: Arc) -> Arc { Arc::new(sends::ClientSends(self.0.clone())) } + + /// Attachment file operations + pub fn attachments(self: Arc) -> Arc { + Arc::new(attachments::ClientAttachments(self.0.clone())) + } + + /// Generate a TOTP code from a provided key. + /// + /// The key can be either: + /// - A base32 encoded string + /// - OTP Auth URI + /// - Steam URI + pub async fn generate_totp( + &self, + key: String, + time: Option>, + ) -> Result { + Ok(self.0 .0.read().await.vault().generate_totp(key, time)?) + } } diff --git a/crates/bitwarden-uniffi/src/vault/password_history.rs b/crates/bitwarden-uniffi/src/vault/password_history.rs index 9042aa8ad..470e28822 100644 --- a/crates/bitwarden-uniffi/src/vault/password_history.rs +++ b/crates/bitwarden-uniffi/src/vault/password_history.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientPasswordHistory(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientPasswordHistory { /// Encrypt password history pub async fn encrypt(&self, password_history: PasswordHistoryView) -> Result { diff --git a/crates/bitwarden-uniffi/src/vault/sends.rs b/crates/bitwarden-uniffi/src/vault/sends.rs index 6e2f1b879..e306168b8 100644 --- a/crates/bitwarden-uniffi/src/vault/sends.rs +++ b/crates/bitwarden-uniffi/src/vault/sends.rs @@ -7,7 +7,7 @@ use crate::{Client, Result}; #[derive(uniffi::Object)] pub struct ClientSends(pub Arc); -#[uniffi::export] +#[uniffi::export(async_runtime = "tokio")] impl ClientSends { /// Encrypt send pub async fn encrypt(&self, send: SendView) -> Result { diff --git a/crates/bitwarden-uniffi/uniffi.toml b/crates/bitwarden-uniffi/uniffi.toml index 456a3676e..8abfa33fc 100644 --- a/crates/bitwarden-uniffi/uniffi.toml +++ b/crates/bitwarden-uniffi/uniffi.toml @@ -1,7 +1,10 @@ [bindings.kotlin] package_name = "com.bitwarden.sdk" cdylib_name = "bitwarden_uniffi" +generate_immutable_records = true +android = true [bindings.swift] ffi_module_name = "BitwardenFFI" module_name = "BitwardenSDK" +generate_immutable_records = true diff --git a/crates/bitwarden-wasm/Cargo.toml b/crates/bitwarden-wasm/Cargo.toml index 6055388fb..a4ba8b6ae 100644 --- a/crates/bitwarden-wasm/Cargo.toml +++ b/crates/bitwarden-wasm/Cargo.toml @@ -1,23 +1,38 @@ [package] name = "bitwarden-wasm" version = "0.1.0" -edition = "2021" -rust-version = "1.57" +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 -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] [dependencies] -js-sys = "0.3.63" -serde = {version = "1.0.163", features = ["derive"] } -wasm-bindgen = { version = "0.2.86", features = ["serde-serialize"] } -wasm-bindgen-futures = "0.4.36" +argon2 = { version = ">=0.5.0, <0.6", features = [ + "alloc", + "zeroize", +], default-features = false } +bitwarden-json = { path = "../bitwarden-json", features = [ + "secrets", + "internal", +] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } -log = "0.4.18" - -bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] } +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"] } +wasm-bindgen-futures = "0.4.41" [dev-dependencies] -wasm-bindgen-test = "0.3.36" +wasm-bindgen-test = "0.3.41" + +[lints] +workspace = true diff --git a/crates/bitwarden-wasm/LICENSE b/crates/bitwarden-wasm/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden-wasm/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden-wasm/README.md b/crates/bitwarden-wasm/README.md new file mode 100644 index 000000000..6fa8870f2 --- /dev/null +++ b/crates/bitwarden-wasm/README.md @@ -0,0 +1,23 @@ +# Bitwarden-wasm + +Requirements: + +- `wasm32-unknown-unknown` rust target. +- `wasm-bindgen-cli` installed. +- `binaryen` installed for `wasm-opt` and `wasm2js`. + +```bash +rustup target add wasm32-unknown-unknown +cargo install -f wasm-bindgen-cli +brew install binaryen +``` + +#### Build + +```bash +# dev +./build.sh + +# release +./build.sh -r +``` diff --git a/crates/bitwarden-wasm/build.sh b/crates/bitwarden-wasm/build.sh new file mode 100755 index 000000000..ae32c775d --- /dev/null +++ b/crates/bitwarden-wasm/build.sh @@ -0,0 +1,23 @@ +# Move to the root of the repository +cd "$(dirname "$0")" +cd ../../ + +if [ "$1" != "-r" ]; then + # Dev + cargo build -p bitwarden -p bitwarden-wasm --target wasm32-unknown-unknown --features wasm-bindgen + wasm-bindgen --target bundler --out-dir languages/js/wasm ./target/wasm32-unknown-unknown/debug/bitwarden_wasm.wasm + wasm-bindgen --target nodejs --out-dir languages/js/wasm/node ./target/wasm32-unknown-unknown/debug/bitwarden_wasm.wasm +else + # Release + cargo build -p bitwarden -p bitwarden-wasm --target wasm32-unknown-unknown --features wasm-bindgen --release + wasm-bindgen --target bundler --out-dir languages/js/wasm ./target/wasm32-unknown-unknown/release/bitwarden_wasm.wasm + wasm-bindgen --target nodejs --out-dir languages/js/wasm/node ./target/wasm32-unknown-unknown/release/bitwarden_wasm.wasm +fi + +# Optimize size +wasm-opt -Os ./languages/js/wasm/bitwarden_wasm_bg.wasm -o ./languages/js/wasm/bitwarden_wasm_bg.wasm +wasm-opt -Os ./languages/js/wasm/node/bitwarden_wasm_bg.wasm -o ./languages/js/wasm/node/bitwarden_wasm_bg.wasm + +# Transpile to JS +wasm2js ./languages/js/wasm/bitwarden_wasm_bg.wasm -o ./languages/js/wasm/bitwarden_wasm_bg.wasm.js +npx terser ./languages/js/wasm/bitwarden_wasm_bg.wasm.js -o ./languages/js/wasm/bitwarden_wasm_bg.wasm.js diff --git a/crates/bitwarden-wasm/src/client.rs b/crates/bitwarden-wasm/src/client.rs index 7d3d991db..bca8c2383 100644 --- a/crates/bitwarden-wasm/src/client.rs +++ b/crates/bitwarden-wasm/src/client.rs @@ -1,6 +1,7 @@ extern crate console_error_panic_hook; -use std::{rc::Rc, sync::RwLock}; +use std::rc::Rc; +use argon2::{Algorithm, Argon2, Params, Version}; use bitwarden_json::client::Client as JsonClient; use js_sys::Promise; use log::Level; @@ -26,10 +27,10 @@ fn convert_level(level: LogLevel) -> Level { } } -// 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 +// 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(Rc>); +pub struct BitwardenClient(Rc); #[wasm_bindgen] impl BitwardenClient { @@ -42,21 +43,39 @@ impl BitwardenClient { panic!("failed to initialize logger: {:?}", e); } - Self(Rc::new(RwLock::new(bitwarden_json::client::Client::new( - settings_input, - )))) + Self(Rc::new(bitwarden_json::client::Client::new(settings_input))) } #[wasm_bindgen] - pub fn run_command(&mut self, js_input: String) -> Promise { + pub fn run_command(&self, js_input: String) -> Promise { let rc = self.0.clone(); - // TODO: We should probably switch to an async-aware RwLock here, - // but it probably doesn't matter much in a single threaded environment - #[allow(clippy::await_holding_lock)] future_to_promise(async move { - let mut client = rc.write().unwrap(); - let result = client.run_command(&js_input).await; + let result = rc.run_command(&js_input).await; Ok(result.into()) }) } } + +#[wasm_bindgen] +pub fn argon2( + password: &[u8], + salt: &[u8], + iterations: u32, + memory: u32, + parallelism: u32, +) -> Result, JsError> { + let argon = Argon2::new( + Algorithm::Argon2id, + Version::V0x13, + Params::new( + memory * 1024, // Convert MiB to KiB + iterations, + parallelism, + Some(32), + )?, + ); + + let mut hash = [0u8; 32]; + argon.hash_password_into(password, salt, &mut hash)?; + Ok(hash.to_vec()) +} diff --git a/crates/bitwarden/CHANGELOG.md b/crates/bitwarden/CHANGELOG.md index d61af5206..ee4c6033b 100644 --- a/crates/bitwarden/CHANGELOG.md +++ b/crates/bitwarden/CHANGELOG.md @@ -9,8 +9,26 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed -- `auth::request::AccessTokenLoginRequest` moved to `auth::login::AccessTokenLoginRequest` (#178) +- Switched TLS backend to `rustls`, removing the dependency on `OpenSSL`. (#374) +- `client::AccessToken` is now `auth::AccessToken`. (#656) + +## [0.4.0] - 2023-12-21 + +### Added + +- Support for basic state to avoid reauthenticating when creating a new `Client`. This is a breaking + change because of adding `state_file` to the `AccessTokenLoginRequest` struct. (#388) + +### Deprecated + +- `client.access_token_login()` is now deprecated and will be removed in a future release. Please + use `client.auth().login_access_token()` instead. (#319) + +## [0.3.1] - 2023-10-13 +### Changed + +- `auth::request::AccessTokenLoginRequest` moved to `auth::login::AccessTokenLoginRequest` (#178) - Support for fetching multiple secrets by ids (#150) ## [0.3.0] - 2023-07-26 diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 345b1f9cb..b34f9c9a1 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -1,63 +1,90 @@ [package] name = "bitwarden" -version = "0.3.0" -authors = ["Bitwarden Inc"] -license-file = "LICENSE" -repository = "https://github.com/bitwarden/sdk" -homepage = "https://bitwarden.com" description = """ Bitwarden Secrets Manager SDK """ keywords = ["bitwarden", "secrets-manager"] -edition = "2021" -rust-version = "1.57" + +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true [features] default = ["secrets"] -secrets = [] # Secrets manager API -internal = [] # Internal testing methods -mobile = ["uniffi", "internal"] # Mobile-specific features +secrets = [] # Secrets manager API +internal = [ + "dep:bitwarden-exporters", + "dep:bitwarden-generators", +] # Internal testing methods +mobile = [ + "internal", + "dep:uniffi", + "bitwarden-crypto/mobile", + "bitwarden-generators/mobile", +] # Mobile-specific features + +wasm-bindgen = ["chrono/wasmbind"] [dependencies] base64 = ">=0.21.2, <0.22" -lazy_static = ">=1.4.0, <2.0" -reqwest = { version = ">=0.11, <0.12", features = ["json"] } +bitwarden-api-api = { workspace = true } +bitwarden-api-identity = { workspace = true } +bitwarden-crypto = { workspace = true } +bitwarden-exporters = { workspace = true, optional = true } +bitwarden-generators = { workspace = true, optional = true } +chrono = { version = ">=0.4.26, <0.5", features = [ + "clock", + "serde", + "std", +], default-features = false } +# We don't use this directly (it's used by rand), but we need it here to enable WASM support +getrandom = { version = ">=0.2.9, <0.3", features = ["js"] } +hmac = ">=0.12.1, <0.13" +log = ">=0.4.18, <0.5" +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.13" serde_repr = ">=0.1.12, <0.2" -schemars = { version = ">=0.8, <0.9", features = ["uuid1", "chrono"] } -log = ">=0.4.18, <0.5" -assert_matches = ">=1.5.0, <2.0" -thiserror = ">=1.0.40, <2.0" -aes = ">=0.8.2, <0.9" -cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } -hkdf = ">=0.12.3, <0.13" -hmac = ">=0.12.1, <0.13" -rsa = ">=0.9.2, <0.10" sha1 = ">=0.10.5, <0.11" sha2 = ">=0.10.6, <0.11" -pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false } -argon2 = { version = ">=0.5.0, <0.6", features = [ - "alloc", -], default-features = false } -rand = ">=0.8.5, <0.9" -num-bigint = ">=0.4, <0.5" -num-traits = ">=0.2.15, <0.3" +thiserror = ">=1.0.40, <2.0" +uniffi = { version = "=0.26.1", optional = true, features = ["tokio"] } uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } -chrono = { version = ">=0.4.26, <0.5", features = [ - "serde", - "std", -], default-features = false } -uniffi = { version = "=0.24.1", optional = true } +zxcvbn = ">= 2.2.2, <3.0" -# We don't use this directly (it's used by rand), but we need it here to enable WASM support -getrandom = { version = ">=0.2.9", features = ["js"] } +[target.'cfg(all(not(target_os = "android"), not(target_arch="wasm32")))'.dependencies] +# By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates +# There are a few exceptions to this: +# - WASM doesn't require a TLS stack, as it just uses the browsers/node fetch +# - Android uses webpki-roots for the moment +reqwest = { version = "*", features = [ + "rustls-tls-manual-roots", +], default-features = false } +rustls-platform-verifier = "0.2.0" -bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.1" } -bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.1" } +[target.'cfg(target_os = "android")'.dependencies] +# On android, the use of rustls-platform-verifier is more complicated and going through some changes at the moment, so we fall back to using webpki-roots +# This means that for the moment android won't support self-signed certificates, even if they are included in the OS trust store +reqwest = { version = "*", features = [ + "rustls-tls-webpki-roots", +], default-features = false } [dev-dependencies] -tokio = { version = "1.28.2", features = ["rt", "macros"] } -wiremock = "0.5.18" +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"] } + +[lints] +workspace = true diff --git a/crates/bitwarden/LICENSE b/crates/bitwarden/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bitwarden/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bitwarden/README.md b/crates/bitwarden/README.md index a0708b7e6..0846651eb 100644 --- a/crates/bitwarden/README.md +++ b/crates/bitwarden/README.md @@ -13,7 +13,7 @@ bitwarden = { "*", features = ["secrets"] } ## Minimum Supported Rust Version -Rust **1.57** or higher. +Rust **1.71** or higher. ## Example @@ -41,8 +41,8 @@ async fn test() -> Result<()> { let mut client = Client::new(Some(settings)); // Before we operate, we need to authenticate with a token - let token = AccessTokenLoginRequest { access_token: String::from("") }; - client.access_token_login(&token).await.unwrap(); + let token = AccessTokenLoginRequest { access_token: String::from(""), state_file: None }; + client.auth().login_access_token(&token).await.unwrap(); let org_id = SecretIdentifiersRequest { organization_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() }; println!("Stored secrets: {:#?}", client.secrets().list(&org_id).await.unwrap()); diff --git a/crates/bitwarden/src/admin_console/mod.rs b/crates/bitwarden/src/admin_console/mod.rs new file mode 100644 index 000000000..e553456cf --- /dev/null +++ b/crates/bitwarden/src/admin_console/mod.rs @@ -0,0 +1,3 @@ +mod policy; + +pub use policy::Policy; diff --git a/crates/bitwarden/src/admin_console/policy.rs b/crates/bitwarden/src/admin_console/policy.rs new file mode 100644 index 000000000..d8ed0b761 --- /dev/null +++ b/crates/bitwarden/src/admin_console/policy.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; + +use bitwarden_api_api::models::PolicyResponseModel; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use uuid::Uuid; + +use crate::error::{require, Error, Result}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub struct Policy { + id: Uuid, + organization_id: Uuid, + r#type: PolicyType, + data: Option>, + enabled: bool, +} + +#[derive(Serialize_repr, Deserialize_repr, Debug, JsonSchema)] +#[repr(u8)] +pub enum PolicyType { + TwoFactorAuthentication = 0, // Requires users to have 2fa enabled + MasterPassword = 1, // Sets minimum requirements for master password complexity + PasswordGenerator = 2, /* Sets minimum requirements/default type for generated + * passwords/passphrases */ + SingleOrg = 3, // Allows users to only be apart of one organization + RequireSso = 4, // Requires users to authenticate with SSO + PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items + DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends + SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends + ResetPassword = 8, /* Allows orgs to use reset password : also can enable + * auto-enrollment during invite flow */ + MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout + DisablePersonalVaultExport = 10, // Disable personal vault export + ActivateAutofill = 11, // Activates autofill with page load on the browser extension +} + +impl TryFrom for Policy { + type Error = Error; + + fn try_from(policy: PolicyResponseModel) -> Result { + Ok(Self { + id: require!(policy.id), + organization_id: require!(policy.organization_id), + r#type: require!(policy.r#type).into(), + data: policy.data, + enabled: require!(policy.enabled), + }) + } +} + +impl From for PolicyType { + fn from(policy_type: bitwarden_api_api::models::PolicyType) -> Self { + match policy_type { + bitwarden_api_api::models::PolicyType::TwoFactorAuthentication => { + PolicyType::TwoFactorAuthentication + } + bitwarden_api_api::models::PolicyType::MasterPassword => PolicyType::MasterPassword, + bitwarden_api_api::models::PolicyType::PasswordGenerator => { + PolicyType::PasswordGenerator + } + bitwarden_api_api::models::PolicyType::SingleOrg => PolicyType::SingleOrg, + bitwarden_api_api::models::PolicyType::RequireSso => PolicyType::RequireSso, + bitwarden_api_api::models::PolicyType::PersonalOwnership => { + PolicyType::PersonalOwnership + } + bitwarden_api_api::models::PolicyType::DisableSend => PolicyType::DisableSend, + bitwarden_api_api::models::PolicyType::SendOptions => PolicyType::SendOptions, + bitwarden_api_api::models::PolicyType::ResetPassword => PolicyType::ResetPassword, + bitwarden_api_api::models::PolicyType::MaximumVaultTimeout => { + PolicyType::MaximumVaultTimeout + } + bitwarden_api_api::models::PolicyType::DisablePersonalVaultExport => { + PolicyType::DisablePersonalVaultExport + } + bitwarden_api_api::models::PolicyType::ActivateAutofill => PolicyType::ActivateAutofill, + } + } +} diff --git a/crates/bitwarden/src/client/access_token.rs b/crates/bitwarden/src/auth/access_token.rs similarity index 73% rename from crates/bitwarden/src/client/access_token.rs rename to crates/bitwarden/src/auth/access_token.rs index b68d78572..4b6f050c8 100644 --- a/crates/bitwarden/src/client/access_token.rs +++ b/crates/bitwarden/src/auth/access_token.rs @@ -1,20 +1,26 @@ -use std::str::FromStr; +use std::{fmt::Debug, str::FromStr}; use base64::Engine; +use bitwarden_crypto::{derive_shareable_key, SymmetricCryptoKey}; use uuid::Uuid; -use crate::{ - crypto::{derive_shareable_key, SymmetricCryptoKey}, - error::AccessTokenInvalidError, - util::BASE64_ENGINE, -}; +use crate::{error::AccessTokenInvalidError, util::STANDARD_INDIFFERENT}; pub struct AccessToken { - pub service_account_id: Uuid, + pub access_token_id: Uuid, pub client_secret: String, pub encryption_key: SymmetricCryptoKey, } +// We don't want to log the more sensitive fields from an AccessToken +impl Debug for AccessToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AccessToken") + .field("access_token_id", &self.access_token_id) + .finish() + } +} + impl FromStr for AccessToken { type Err = crate::error::Error; @@ -22,7 +28,7 @@ impl FromStr for AccessToken { let (first_part, encryption_key) = key.split_once(':').ok_or(AccessTokenInvalidError::NoKey)?; - let [version, service_account_id, client_secret]: [&str; 3] = first_part + let [version, access_token_id, client_secret]: [&str; 3] = first_part .split('.') .collect::>() .try_into() @@ -32,11 +38,11 @@ impl FromStr for AccessToken { return Err(AccessTokenInvalidError::WrongVersion.into()); } - let Ok(service_account_id) = service_account_id.parse() else { + let Ok(access_token_id) = access_token_id.parse() else { return Err(AccessTokenInvalidError::InvalidUuid.into()); }; - let encryption_key = BASE64_ENGINE + let encryption_key = STANDARD_INDIFFERENT .decode(encryption_key) .map_err(AccessTokenInvalidError::InvalidBase64)?; let encryption_key: [u8; 16] = encryption_key.try_into().map_err(|e: Vec<_>| { @@ -49,7 +55,7 @@ impl FromStr for AccessToken { derive_shareable_key(encryption_key, "accesstoken", Some("sm-access-token")); Ok(AccessToken { - service_account_id, + access_token_id, client_secret: client_secret.to_owned(), encryption_key, }) @@ -59,30 +65,29 @@ impl FromStr for AccessToken { #[cfg(test)] mod tests { + use super::AccessToken; + #[test] fn can_decode_access_token() { use std::str::FromStr; - use crate::client::AccessToken; - let access_token = "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ=="; let token = AccessToken::from_str(access_token).unwrap(); assert_eq!( - &token.service_account_id.to_string(), + &token.access_token_id.to_string(), "ec2c1d46-6a4b-4751-a310-af9601317f2d" ); assert_eq!(token.client_secret, "C2IgxjjLF7qSshsbwe8JGcbM075YXw"); - assert_eq!(token.encryption_key.to_base64(), "H9/oIRLtL9nGCQOVDjSMoEbJsjWXSOCb3qeyDt6ckzS3FhyboEDWyTP/CQfbIszNmAVg2ExFganG1FVFGXO/Jg=="); + assert_eq!(token.encryption_key.to_base64().expose(), "H9/oIRLtL9nGCQOVDjSMoEbJsjWXSOCb3qeyDt6ckzS3FhyboEDWyTP/CQfbIszNmAVg2ExFganG1FVFGXO/Jg=="); } #[test] fn malformed_tokens() { use std::str::FromStr; - use crate::client::AccessToken; - - // Encryption key without base64 padding, we generate it with padding but ignore it when decoding + // Encryption key without base64 padding, we generate it with padding but ignore it when + // decoding let t = "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ"; assert!(AccessToken::from_str(t).is_ok()); diff --git a/crates/bitwarden/src/auth/api/request/access_token_request.rs b/crates/bitwarden/src/auth/api/request/access_token_request.rs index b5035269b..9f41d6b40 100644 --- a/crates/bitwarden/src/auth/api/request/access_token_request.rs +++ b/crates/bitwarden/src/auth/api/request/access_token_request.rs @@ -13,10 +13,10 @@ pub struct AccessTokenRequest { } impl AccessTokenRequest { - pub fn new(service_account_id: Uuid, client_secret: &String) -> Self { + pub fn new(access_token_id: Uuid, client_secret: &String) -> Self { let obj = Self { scope: "api.secrets".to_string(), - client_id: service_account_id.to_string(), + client_id: access_token_id.to_string(), client_secret: client_secret.to_string(), grant_type: "client_credentials".to_string(), }; diff --git a/crates/bitwarden/src/auth/api/request/auth_request_token_request.rs b/crates/bitwarden/src/auth/api/request/auth_request_token_request.rs new file mode 100644 index 000000000..cf5ae7ee4 --- /dev/null +++ b/crates/bitwarden/src/auth/api/request/auth_request_token_request.rs @@ -0,0 +1,59 @@ +use log::debug; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::{ + auth::api::response::IdentityTokenResponse, + client::{client_settings::DeviceType, ApiConfigurations}, + error::Result, +}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct AuthRequestTokenRequest { + scope: String, + client_id: String, + #[serde(rename = "deviceType")] + device_type: u8, + #[serde(rename = "deviceIdentifier")] + device_identifier: String, + #[serde(rename = "deviceName")] + device_name: String, + grant_type: String, + #[serde(rename = "username")] + email: String, + #[serde(rename = "authRequest")] + auth_request_id: Uuid, + #[serde(rename = "password")] + access_code: String, +} + +impl AuthRequestTokenRequest { + pub fn new( + email: &str, + auth_request_id: &Uuid, + access_code: &str, + device_type: DeviceType, + device_identifier: &str, + ) -> Self { + let obj = Self { + scope: "api offline_access".to_string(), + client_id: "web".to_string(), + device_type: device_type as u8, + device_identifier: device_identifier.to_string(), + device_name: "chrome".to_string(), + grant_type: "password".to_string(), + email: email.to_string(), + auth_request_id: *auth_request_id, + access_code: access_code.to_string(), + }; + debug!("initializing {:?}", obj); + obj + } + + pub(crate) async fn send( + &self, + configurations: &ApiConfigurations, + ) -> Result { + super::send_identity_connect_request(configurations, Some(&self.email), &self).await + } +} diff --git a/crates/bitwarden/src/auth/api/request/mod.rs b/crates/bitwarden/src/auth/api/request/mod.rs index 1277fa969..263d28357 100644 --- a/crates/bitwarden/src/auth/api/request/mod.rs +++ b/crates/bitwarden/src/auth/api/request/mod.rs @@ -9,17 +9,21 @@ mod renew_token_request; pub(crate) use access_token_request::*; #[cfg(feature = "internal")] pub(crate) use api_token_request::*; -use base64::Engine; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; #[cfg(feature = "internal")] pub(crate) use password_token_request::*; #[cfg(feature = "internal")] pub(crate) use renew_token_request::*; +#[cfg(feature = "mobile")] +mod auth_request_token_request; +#[cfg(feature = "mobile")] +pub(crate) use auth_request_token_request::*; + use crate::{ auth::api::response::{parse_identity_response, IdentityTokenResponse}, client::ApiConfigurations, error::Result, - util::BASE64_ENGINE, }; async fn send_identity_connect_request( @@ -35,10 +39,10 @@ async fn send_identity_connect_request( &configurations.identity.base_path )) .header( - "Content-Type", + reqwest::header::CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8", ) - .header("Accept", "application/json") + .header(reqwest::header::ACCEPT, "application/json") .header("Device-Type", configurations.device_type as usize); if let Some(ref user_agent) = configurations.identity.user_agent { @@ -46,11 +50,11 @@ async fn send_identity_connect_request( } if let Some(email) = email { - request = request.header("Auth-Email", BASE64_ENGINE.encode(email.as_bytes())); + request = request.header("Auth-Email", URL_SAFE_NO_PAD.encode(email.as_bytes())); } let response = request - .body(serde_qs::to_string(&body).unwrap()) + .body(serde_qs::to_string(&body).expect("Serialize should be infallible")) .send() .await?; diff --git a/crates/bitwarden/src/auth/api/request/password_token_request.rs b/crates/bitwarden/src/auth/api/request/password_token_request.rs index fd016d898..2f6414bcd 100644 --- a/crates/bitwarden/src/auth/api/request/password_token_request.rs +++ b/crates/bitwarden/src/auth/api/request/password_token_request.rs @@ -6,7 +6,7 @@ use crate::{ api::response::IdentityTokenResponse, login::{TwoFactorProvider, TwoFactorRequest}, }, - client::ApiConfigurations, + client::{client_settings::DeviceType, ApiConfigurations}, error::Result, }; @@ -35,13 +35,19 @@ pub struct PasswordTokenRequest { } impl PasswordTokenRequest { - pub fn new(email: &str, password_hash: &String, two_factor: &Option) -> Self { + pub fn new( + email: &str, + password_hash: &str, + device_type: DeviceType, + device_identifier: &str, + two_factor: &Option, + ) -> Self { let tf = two_factor.as_ref(); let obj = Self { scope: "api offline_access".to_string(), client_id: "web".to_string(), - device_type: 10, - device_identifier: "b86dd6ab-4265-4ddf-a7f1-eb28d5677f33".to_string(), + device_type: device_type as u8, + device_identifier: device_identifier.to_string(), device_name: "firefox".to_string(), grant_type: "password".to_string(), master_password_hash: password_hash.to_string(), diff --git a/crates/bitwarden/src/auth/api/response/identity_payload_response.rs b/crates/bitwarden/src/auth/api/response/identity_payload_response.rs index b35b398d7..47543510b 100644 --- a/crates/bitwarden/src/auth/api/response/identity_payload_response.rs +++ b/crates/bitwarden/src/auth/api/response/identity_payload_response.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq)] +#[cfg_attr(test, derive(Default))] pub struct IdentityTokenPayloadResponse { pub access_token: String, pub expires_in: u64, @@ -10,21 +11,3 @@ pub struct IdentityTokenPayloadResponse { pub(crate) encrypted_payload: String, } - -#[cfg(test)] -mod test { - use super::*; - - impl Default for IdentityTokenPayloadResponse { - fn default() -> Self { - Self { - access_token: Default::default(), - expires_in: Default::default(), - refresh_token: Default::default(), - token_type: Default::default(), - scope: Default::default(), - encrypted_payload: Default::default(), - } - } - } -} diff --git a/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs b/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs index eaf5c142f..c5733bd36 100644 --- a/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs +++ b/crates/bitwarden/src/auth/api/response/identity_refresh_response.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq)] +#[cfg_attr(test, derive(Default))] pub struct IdentityTokenRefreshResponse { pub access_token: String, pub expires_in: u64, @@ -8,20 +9,3 @@ pub struct IdentityTokenRefreshResponse { token_type: String, scope: String, } - -#[cfg(test)] -mod test { - use super::*; - - impl Default for IdentityTokenRefreshResponse { - fn default() -> Self { - Self { - access_token: Default::default(), - expires_in: Default::default(), - refresh_token: Default::default(), - token_type: Default::default(), - scope: Default::default(), - } - } - } -} diff --git a/crates/bitwarden/src/auth/api/response/identity_success_response.rs b/crates/bitwarden/src/auth/api/response/identity_success_response.rs index e59f91b7b..94ebe9445 100644 --- a/crates/bitwarden/src/auth/api/response/identity_success_response.rs +++ b/crates/bitwarden/src/auth/api/response/identity_success_response.rs @@ -22,7 +22,7 @@ pub struct IdentityTokenSuccessResponse { #[serde( rename = "kdfIterations", alias = "KdfIterations", - default = "crate::util::default_pbkdf2_iterations" + default = "bitwarden_crypto::default_pbkdf2_iterations" )] kdf_iterations: NonZeroU32, @@ -41,6 +41,8 @@ pub struct IdentityTokenSuccessResponse { #[cfg(test)] mod test { + use bitwarden_crypto::default_pbkdf2_iterations; + use super::*; impl Default for IdentityTokenSuccessResponse { @@ -53,8 +55,8 @@ mod test { private_key: Default::default(), key: Default::default(), two_factor_token: Default::default(), - kdf: KdfType::Variant0, - kdf_iterations: crate::util::default_pbkdf2_iterations(), + kdf: KdfType::default(), + kdf_iterations: default_pbkdf2_iterations(), reset_master_password: Default::default(), force_password_reset: Default::default(), api_use_key_connector: Default::default(), diff --git a/crates/bitwarden/src/auth/api/response/identity_token_response.rs b/crates/bitwarden/src/auth/api/response/identity_token_response.rs index ae6cc81e5..b22263bc5 100644 --- a/crates/bitwarden/src/auth/api/response/identity_token_response.rs +++ b/crates/bitwarden/src/auth/api/response/identity_token_response.rs @@ -57,7 +57,7 @@ mod test { #[test] fn two_factor() { - let expected = Box::new(IdentityTwoFactorResponse::default()); + let expected = Box::::default(); let two_factor = serde_json::to_string(&expected).unwrap(); let expected = IdentityTokenResponse::TwoFactorRequired(expected); let actual = parse_identity_response(StatusCode::BAD_REQUEST, two_factor).unwrap(); diff --git a/crates/bitwarden/src/auth/auth_request.rs b/crates/bitwarden/src/auth/auth_request.rs new file mode 100644 index 000000000..04dc9fdce --- /dev/null +++ b/crates/bitwarden/src/auth/auth_request.rs @@ -0,0 +1,260 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::{ + fingerprint, AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, +}; +#[cfg(feature = "mobile")] +use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; +use bitwarden_generators::{password, PasswordGeneratorRequest}; + +use crate::{error::Error, Client}; + +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct AuthRequestResponse { + /// Base64 encoded private key + /// This key is temporarily passed back and will most likely not be available in the future + pub private_key: String, + /// Base64 encoded public key + pub public_key: String, + /// Fingerprint of the public key + pub fingerprint: String, + /// Access code + pub access_code: String, +} + +/// Initiate a new auth request. +/// +/// Generates a private key and access code. The pulic key is uploaded to the server and transmitted +/// to another device. Where the user confirms the validity by confirming the fingerprint. The user +/// key is then encrypted using the public key and returned to the initiating device. +pub(crate) fn new_auth_request(email: &str) -> Result { + let mut rng = rand::thread_rng(); + + let key = AsymmetricCryptoKey::generate(&mut rng); + + let spki = key.to_public_der()?; + + let fingerprint = fingerprint(email, &spki)?; + let b64 = STANDARD.encode(&spki); + + Ok(AuthRequestResponse { + private_key: STANDARD.encode(key.to_der()?), + public_key: b64, + fingerprint, + access_code: password(PasswordGeneratorRequest { + length: 25, + lowercase: true, + uppercase: true, + numbers: true, + special: false, + ..Default::default() + })?, + }) +} + +/// Decrypt the user key using the private key generated previously. +#[cfg(feature = "mobile")] +pub(crate) fn auth_request_decrypt_user_key( + private_key: String, + user_key: AsymmetricEncString, +) -> Result { + let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; + let mut key: Vec = user_key.decrypt_with_key(&key)?; + + Ok(SymmetricCryptoKey::try_from(key.as_mut_slice())?) +} + +/// Decrypt the user key using the private key generated previously. +#[cfg(feature = "mobile")] +pub(crate) fn auth_request_decrypt_master_key( + private_key: String, + master_key: AsymmetricEncString, + user_key: EncString, +) -> Result { + use bitwarden_crypto::MasterKey; + + let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; + let mut master_key: Vec = master_key.decrypt_with_key(&key)?; + let master_key = MasterKey::new(SymmetricCryptoKey::try_from(master_key.as_mut_slice())?); + + Ok(master_key.decrypt_user_key(user_key)?) +} + +/// Approve an auth request. +/// +/// Encrypts the user key with a public key. +pub(crate) fn approve_auth_request( + client: &mut 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)?; + + Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( + key.to_vec().expose(), + &public_key, + )?) +} + +#[test] +fn test_auth_request() { + let request = new_auth_request("test@bitwarden.com").unwrap(); + + let secret: &[u8] = &[ + 111, 32, 97, 169, 4, 241, 174, 74, 239, 206, 113, 86, 174, 68, 216, 238, 52, 85, 156, 27, + 134, 149, 54, 55, 91, 147, 45, 130, 131, 237, 51, 31, 191, 106, 155, 14, 160, 82, 47, 40, + 96, 31, 114, 127, 212, 187, 167, 110, 205, 116, 198, 243, 218, 72, 137, 53, 248, 43, 255, + 67, 35, 61, 245, 93, + ]; + + let private_key = + AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); + + let encrypted = AsymmetricEncString::encrypt_rsa2048_oaep_sha1(secret, &private_key).unwrap(); + + let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap(); + + assert_eq!(decrypted.to_vec().expose(), secret); +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use bitwarden_crypto::Kdf; + + use super::*; + use crate::{ + client::{LoginMethod, UserLoginMethod}, + mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}, + }; + + #[test] + fn test_approve() { + 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(), + }, + })); + + 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 + .initialize_user_crypto("asdfasdfasdf", user_key, private_key) + .unwrap(); + + let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvyLRDUwXB4BfQ507D4meFPmwn5zwy3IqTPJO4plrrhnclWahXa240BzyFW9gHgYu+Jrgms5xBfRTBMcEsqqNm7+JpB6C1B6yvnik0DpJgWQw1rwvy4SUYidpR/AWbQi47n/hvnmzI/sQxGddVfvWu1iTKOlf5blbKYAXnUE5DZBGnrWfacNXwRRdtP06tFB0LwDgw+91CeLSJ9py6dm1qX5JIxoO8StJOQl65goLCdrTWlox+0Jh4xFUfCkb+s3px+OhSCzJbvG/hlrSRcUz5GnwlCEyF3v5lfUtV96MJD+78d8pmH6CfFAp2wxKRAbGdk+JccJYO6y6oIXd3Fm7twIDAQAB"; + + // Verify fingerprint + let pbkey = STANDARD.decode(public_key).unwrap(); + 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(); + } + + #[tokio::test] + async fn test_decrypt_user_key() { + 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 enc_user_key = "4.dxbd5OMwi/Avy7DQxvLV+Z7kDJgHBtg/jAbgYNO7QU0Zii4rLFNco2lS5aS9z42LTZHc2p5HYwn2ZwkZNfHsQ6//d5q40MDgGYJMKBXOZP62ZHhct1XsvYBmtcUtIOm5j2HSjt2pjEuGAc1LbyGIWRJJQ3Lp1ULbL2m71I+P23GF36JyOM8SUWvpvxE/3+qqVhRFPG2VqMCYa2kLLxwVfUmpV+KKjX1TXsrq6pfJIwHNwHw4h7MSfD8xTy2bx4MiBt638Z9Vt1pGsSQkh9RgPvCbnhuCpZQloUgJ8ByLVEcrlKx3yaaxiQXvte+ZhuOI7rGdjmoVoOzisooje4JgYw==".parse().unwrap(); + let dec = auth_request_decrypt_user_key(private_key.to_owned(), enc_user_key).unwrap(); + + assert_eq!( + dec.to_vec().expose(), + &[ + 201, 37, 234, 213, 21, 75, 40, 70, 149, 213, 234, 16, 19, 251, 162, 245, 161, 74, + 34, 245, 211, 151, 211, 192, 95, 10, 117, 50, 88, 223, 23, 157 + ] + ); + } + + #[tokio::test] + async fn test_decrypt_master_key() { + 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 enc_master_key = "4.dxbd5OMwi/Avy7DQxvLV+Z7kDJgHBtg/jAbgYNO7QU0Zii4rLFNco2lS5aS9z42LTZHc2p5HYwn2ZwkZNfHsQ6//d5q40MDgGYJMKBXOZP62ZHhct1XsvYBmtcUtIOm5j2HSjt2pjEuGAc1LbyGIWRJJQ3Lp1ULbL2m71I+P23GF36JyOM8SUWvpvxE/3+qqVhRFPG2VqMCYa2kLLxwVfUmpV+KKjX1TXsrq6pfJIwHNwHw4h7MSfD8xTy2bx4MiBt638Z9Vt1pGsSQkh9RgPvCbnhuCpZQloUgJ8ByLVEcrlKx3yaaxiQXvte+ZhuOI7rGdjmoVoOzisooje4JgYw==".parse().unwrap(); + let enc_user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); + let dec = + auth_request_decrypt_master_key(private_key.to_owned(), enc_master_key, enc_user_key) + .unwrap(); + + assert_eq!( + dec.to_vec().expose(), + &[ + 109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216, + 212, 173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91, + 215, 21, 128, 91, 226, 222, 165, 67, 251, 34, 83, 81, 77, 147, 225, 76, 13, 41, + 102, 45, 183, 218, 106, 89, 254, 208, 251, 101, 130, 10, + ] + ); + } + + #[tokio::test] + async fn test_device_login() { + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + let email = "test@bitwarden.com"; + + 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="; + + // Initialize an existing client which is unlocked + let mut existing_device = Client::new(None); + existing_device.set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "123".to_owned(), + email: email.to_owned(), + kdf: kdf.clone(), + })); + + existing_device + .initialize_user_crypto("asdfasdfasdf", 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); + + // 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(); + + // Unlock the vault using the approved request + new_device + .crypto() + .initialize_user_crypto(InitUserCryptoRequest { + kdf_params: kdf, + email: email.to_owned(), + private_key: private_key.to_owned(), + method: InitUserCryptoMethod::AuthRequest { + request_private_key: auth_req.private_key, + method: AuthRequestMethod::UserKey { + protected_user_key: approved_req, + }, + }, + }) + .await + .unwrap(); + + // We can validate that the vault is unlocked correctly by confirming the user key is the + // same + assert_eq!( + existing_device + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64(), + new_device + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64() + ); + } +} diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs index 8ffca0a44..b3afe133a 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden/src/auth/client_auth.rs @@ -1,14 +1,51 @@ -use super::{ - password::{password_strength, satisfies_policy, MasterPasswordPolicyOptions}, - register::{make_register_keys, register}, - RegisterKeyResponse, RegisterRequest, +#[cfg(feature = "internal")] +use bitwarden_crypto::{AsymmetricEncString, DeviceKey, TrustDeviceResponse}; + +#[cfg(feature = "mobile")] +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, + }, + client::Kdf, + error::Error, }; -use crate::{client::kdf::Kdf, error::Result, Client}; pub struct ClientAuth<'a> { pub(crate) client: &'a mut crate::Client, } +impl<'a> ClientAuth<'a> { + pub async fn renew_token(&mut self) -> Result<()> { + renew_token(self.client).await + } + + #[cfg(feature = "secrets")] + pub async fn login_access_token( + &mut self, + input: &AccessTokenLoginRequest, + ) -> Result { + login_access_token(self.client, input).await + } +} + +#[cfg(feature = "internal")] impl<'a> ClientAuth<'a> { pub async fn password_strength( &self, @@ -37,10 +74,95 @@ impl<'a> ClientAuth<'a> { make_register_keys(email, password, kdf) } - #[cfg(feature = "internal")] + pub fn make_register_tde_keys( + &mut self, + email: String, + org_public_key: String, + remember_device: bool, + ) -> Result { + make_register_tde_keys(self.client, email, org_public_key, remember_device) + } + pub async fn register(&mut self, input: &RegisterRequest) -> Result<()> { register(self.client, input).await } + + pub async fn prelogin(&mut self, email: String) -> Result { + use crate::auth::login::{parse_prelogin, request_prelogin}; + + let response = request_prelogin(self.client, email).await?; + parse_prelogin(response) + } + + pub async fn login_password( + &mut self, + input: &PasswordLoginRequest, + ) -> Result { + login_password(self.client, input).await + } + + pub async fn login_api_key( + &mut self, + input: &ApiKeyLoginRequest, + ) -> Result { + login_api_key(self.client, input).await + } + + pub async fn send_two_factor_email(&mut self, tf: &TwoFactorEmailRequest) -> Result<()> { + send_two_factor_email(self.client, tf).await + } + + pub fn validate_password(&self, password: String, password_hash: String) -> Result { + validate_password(self.client, password, password_hash) + } + + pub fn validate_password_user_key( + &self, + password: String, + encrypted_user_key: String, + ) -> Result { + validate_password_user_key(self.client, password, encrypted_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 { + approve_auth_request(self.client, public_key) + } + + pub fn trust_device(&self) -> Result { + trust_device(self.client) + } +} + +#[cfg(feature = "mobile")] +impl<'a> ClientAuth<'a> { + pub async fn login_device( + &mut self, + email: String, + device_identifier: String, + ) -> Result { + use crate::auth::login::send_new_auth_request; + + send_new_auth_request(self.client, email, device_identifier).await + } + + pub async fn login_device_complete(&mut self, auth_req: NewAuthRequestResponse) -> Result<()> { + use crate::auth::login::complete_auth_request; + + complete_auth_request(self.client, auth_req).await + } +} + +#[cfg(feature = "internal")] +fn trust_device(client: &Client) -> Result { + let enc = client.get_encryption_settings()?; + + let user_key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + + Ok(DeviceKey::trust_device(user_key)?) } impl<'a> Client { @@ -48,3 +170,102 @@ impl<'a> Client { ClientAuth { client: self } } } + +#[cfg(test)] +mod tests { + + #[cfg(feature = "secrets")] + #[tokio::test] + async fn test_access_token_login() { + use wiremock::{matchers, Mock, ResponseTemplate}; + + 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![ + Mock::given(matchers::path("/identity/connect/token")) + .respond_with(ResponseTemplate::new(200).set_body_json( + serde_json::json!({ + "access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjMwMURENkE1MEU4NEUxRDA5MUM4MUQzQjAwQkY5MDEwQzg1REJEOUFSUzI1NiIsInR5cCI6\ + ImF0K2p3dCIsIng1dCI6Ik1CM1dwUTZFNGRDUnlCMDdBTC1RRU1oZHZabyJ9.eyJuYmYiOjE2NzUxMDM3ODEsImV4cCI6MTY3NTEwNzM4MSwiaXNzIjo\ + iaHR0cDovL2xvY2FsaG9zdCIsImNsaWVudF9pZCI6ImVjMmMxZDQ2LTZhNGItNDc1MS1hMzEwLWFmOTYwMTMxN2YyZCIsInN1YiI6ImQzNDgwNGNhLTR\ + mNmMtNDM5Mi04NmI3LWFmOTYwMTMxNzVkMCIsIm9yZ2FuaXphdGlvbiI6ImY0ZTQ0YTdmLTExOTAtNDMyYS05ZDRhLWFmOTYwMTMxMjdjYiIsImp0aSI\ + 6IjU3QUU0NzQ0MzIwNzk1RThGQkQ4MUIxNDA2RDQyNTQyIiwiaWF0IjoxNjc1MTAzNzgxLCJzY29wZSI6WyJhcGkuc2VjcmV0cyJdfQ.GRKYzqgJZHEE\ + ZHsJkhVZH8zjYhY3hUvM4rhdV3FU10WlCteZdKHrPIadCUh-Oz9DxIAA2HfALLhj1chL4JgwPmZgPcVS2G8gk8XeBmZXowpVWJ11TXS1gYrM9syXbv9j\ + 0JUCdpeshH7e56WnlpVynyUwIum9hmYGZ_XJUfmGtlKLuNjYnawTwLEeR005uEjxq3qI1kti-WFnw8ciL4a6HLNulgiFw1dAvs4c7J0souShMfrnFO3g\ + SOHff5kKD3hBB9ynDBnJQSFYJ7dFWHIjhqs0Vj-9h0yXXCcHvu7dVGpaiNjNPxbh6YeXnY6UWcmHLDtFYsG2BWcNvVD4-VgGxXt3cMhrn7l3fSYuo32Z\ + Yk4Wop73XuxqF2fmfmBdZqGI1BafhENCcZw_bpPSfK2uHipfztrgYnrzwvzedz0rjFKbhDyrjzuRauX5dqVJ4ntPeT9g_I5n71gLxiP7eClyAx5RxdF6\ + He87NwC8i-hLBhugIvLTiDj-Sk9HvMth6zaD0ebxd56wDjq8-CMG_WcgusDqNzKFHqWNDHBXt8MLeTgZAR2rQMIMFZqFgsJlRflbig8YewmNUA9wAU74\ + TfxLY1foO7Xpg49vceB7C-PlvGi1VtX6F2i0tc_67lA5kWXnnKBPBUyspoIrmAUCwfms5nTTqA9xXAojMhRHAos_OdM", + "expires_in":3600, + "token_type":"Bearer", + "scope":"api.secrets", + "encrypted_payload":"2.E9fE8+M/VWMfhhim1KlCbQ==|eLsHR484S/tJbIkM6spnG/HP65tj9A6Tba7kAAvUp+rYuQmGLixiOCfMsqt5OvBctDfvvr/Aes\ + Bu7cZimPLyOEhqEAjn52jF0eaI38XZfeOG2VJl0LOf60Wkfh3ryAMvfvLj3G4ZCNYU8sNgoC2+IQ==|lNApuCQ4Pyakfo/wwuuajWNaEX/2MW8/3rjXB/V7n+k="}) + )), + Mock::given(matchers::path("/api/organizations/f4e44a7f-1190-432a-9d4a-af96013127cb/secrets")) + .respond_with(ResponseTemplate::new(200).set_body_json( + serde_json::json!({ + "secrets":[{ + "id":"15744a66-341a-4c62-af50-af960166b6bc", + "organizationId":"f4e44a7f-1190-432a-9d4a-af96013127cb", + "key":"2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=", + "creationDate":"2023-01-26T21:46:02.2182556Z", + "revisionDate":"2023-01-26T21:46:02.2182557Z" + }], + "projects":[], + "object":"SecretsWithProjectsList" + }) + )), + Mock::given(matchers::path("/api/secrets/15744a66-341a-4c62-af50-af960166b6bc")) + .respond_with(ResponseTemplate::new(200).set_body_json( + serde_json::json!({ + "id":"15744a66-341a-4c62-af50-af960166b6bc", + "organizationId":"f4e44a7f-1190-432a-9d4a-af96013127cb", + "key":"2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=", + "value":"2.Gl34n9JYABC7V21qHcBzHg==|c1Ds244pob7i+8+MXe4++w==|Shimz/qKMYZmzSFWdeBzFb9dFz7oF6Uv9oqkws7rEe0=", + "note":"2.Cn9ABJy7+WfR4uUHwdYepg==|+nbJyU/6hSknoa5dcEJEUg==|1DTp/ZbwGO3L3RN+VMsCHz8XDr8egn/M5iSitGGysPA=", + "creationDate":"2023-01-26T21:46:02.2182556Z", + "revisionDate":"2023-01-26T21:46:02.2182557Z", + "object":"secret" + }) + )) + ]).await; + + // Test the login is correct and we store the returned organization ID correctly + let res = client + .auth() + .login_access_token(&AccessTokenLoginRequest { + access_token: "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ==".into(), + state_file: None, + }) + .await + .unwrap(); + assert!(res.authenticated); + let organization_id = client.get_access_token_organization().unwrap(); + assert_eq!( + organization_id.to_string(), + "f4e44a7f-1190-432a-9d4a-af96013127cb" + ); + + // Test that we can retrieve the list of secrets correctly + let mut res = client + .secrets() + .list(&SecretIdentifiersRequest { organization_id }) + .await + .unwrap(); + assert_eq!(res.data.len(), 1); + + // Test that given a secret ID we can get it's data + let res = client + .secrets() + .get(&SecretGetRequest { + id: res.data.remove(0).id, + }) + .await + .unwrap(); + assert_eq!(res.key, "TEST"); + assert_eq!(res.note, "TEST"); + assert_eq!(res.value, "TEST"); + } +} diff --git a/crates/bitwarden/src/auth/jwt_token.rs b/crates/bitwarden/src/auth/jwt_token.rs new file mode 100644 index 000000000..f4d4692ee --- /dev/null +++ b/crates/bitwarden/src/auth/jwt_token.rs @@ -0,0 +1,71 @@ +use std::str::FromStr; + +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; + +use crate::error::Result; + +/// A Bitwarden secrets manager JWT Token. +/// +/// References: +/// - +/// - +/// +/// TODO: We need to expand this to support user based JWT tokens. +#[derive(serde::Deserialize)] +pub struct JWTToken { + pub exp: u64, + pub sub: String, + pub email: Option, + pub organization: Option, + pub scope: Vec, +} + +impl FromStr for JWTToken { + type Err = crate::error::Error; + + /// Parses a JWT token from a string. + /// + /// **Note:** This function does not validate the token signature. + fn from_str(s: &str) -> Result { + let split = s.split('.').collect::>(); + if split.len() != 3 { + return Err("JWT token has an invalid number of parts".into()); + } + let decoded = URL_SAFE_NO_PAD.decode(split[1])?; + Ok(serde_json::from_slice(&decoded)?) + } +} + +#[cfg(test)] +mod tests { + use crate::auth::jwt_token::JWTToken; + + #[test] + fn can_decode_jwt() { + let jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjMwMURENkE1MEU4NEUxRDA5MUM4MUQzQjAwQkY5MDEwQz\ + g1REJEOUFSUzI1NiIsInR5cCI6ImF0K2p3dCIsIng1dCI6Ik1CM1dwUTZFNGRDUnlCMDdBTC1RRU1oZHZabyJ9.eyJu\ + YmYiOjE2NzUxMDM1NzcsImV4cCI6MTY3NTEwNzE3NywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsImNsaWVudF9pZCI\ + 6IndlYiIsInN1YiI6ImUyNWQzN2YzLWI2MDMtNDBkZS04NGJhLWFmOTYwMTJmNWE0MiIsImF1dGhfdGltZSI6MTY3NT\ + EwMzU0OSwiaWRwIjoiYml0d2FyZGVuIiwicHJlbWl1bSI6ZmFsc2UsImVtYWlsIjoidGVzdEBiaXR3YXJkZW4uY29tI\ + iwiZW1haWxfdmVyaWZpZWQiOnRydWUsInNzdGFtcCI6IkUzNElDWVhRUFRDS01EVldBREZDNktHNDJCQldJRDdJIiwi\ + bmFtZSI6IlRlc3QiLCJvcmdvd25lciI6ImY0ZTQ0YTdmLTExOTAtNDMyYS05ZDRhLWFmOTYwMTMxMjdjYiIsImRldml\ + jZSI6Ijg5Mjg5M2FiLWRkNDMtNDUwYS04NGI1LWFhOWM1YjdiYjJkOCIsImp0aSI6IkEzMkVFNjY5NDdEQzlDNUE2MT\ + IwRURBRTIwNzc5OUJFIiwiaWF0IjoxNjc1MTAzNTc3LCJzY29wZSI6WyJhcGkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhb\ + XIiOlsiQXBwbGljYXRpb24iXX0.AyDkKvjmyaSPQViQSa2sGTKIkDGrUAtDmwpE57K4DDWT0QvwDe7FMktmwiF4LH36\ + wx_FnpH21VI1pzwJeTHXtaz3niANJtQZjzGFsNAna_95vrsxZC2YizgGlt6mX4YIGmAw9DiYrmaN0BvQOEm_caV_u6f\ + a30iz9Kvjxf7cpzeZvPEysxGpB3k3TRYTkFUdV43HiXdhXMBhyyOpFU6Fk6yA41y7-8bGYc5mYGknWktmPD9Yx-1xKL\ + ftFja1SnCoLPWvDeK60lqWZQiT4tZHCYJ7m0bBNCccYHc2Kk2Bo5-UoyDxazPwsqMxeNfjlaUuj3o5N_uQ-4n_gVbeA\ + qWV2wrel5UhYjWnczMSLBtt9p0W35kkBPt3ZAnRWMtQMPNH04p-_L6cG-Xu6lDksBTwaavcmtnCKG8V91826EiQ8MrF\ + wGWQRZV6tPKTDAYCgSAZGBY3QDmPGT5BeFcg5Ag_nYYIIifKP-kv10v_N-TOcT3NeGBOUlAZ-9m7iT7Rk3vC--SDZdA\ + U5turoBFiiPL2XXfAjM7P0r7J91gfXc0FaD6I2jDxOmym5h7Yn5phLsbC2NlIXkZp54dKHICenPl4ve6ndDIJacVeS5\ + f3LEddAPV8cAFza4DjA8pZJLFrMyRvMXcL_PjKF8qPVzqVWh03lfJ4clOIxR2gOuWIc902Y5E"; + + let token: JWTToken = jwt.parse().unwrap(); + assert_eq!(token.exp, 1675107177); + assert_eq!(token.sub, "e25d37f3-b603-40de-84ba-af96012f5a42"); + assert_eq!(token.email.as_deref(), Some("test@bitwarden.com")); + assert_eq!(token.organization.as_deref(), None); + assert_eq!(token.scope[0], "api"); + assert_eq!(token.scope[1], "offline_access"); + } +} diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index 46fa35779..cca52512b 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -1,70 +1,95 @@ -use std::str::FromStr; +use std::path::{Path, PathBuf}; -use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use bitwarden_crypto::{EncString, KeyDecryptable, SensitiveString, SymmetricCryptoKey}; +use chrono::Utc; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use crate::{ auth::{ api::{request::AccessTokenRequest, response::IdentityTokenResponse}, login::{response::two_factor::TwoFactorProviders, PasswordLoginResponse}, + AccessToken, JWTToken, }, - client::{AccessToken, LoginMethod, ServiceAccountLoginMethod}, - crypto::{EncString, SymmetricCryptoKey}, - error::{Error, Result}, - util::{decode_token, BASE64_ENGINE}, + client::{LoginMethod, ServiceAccountLoginMethod}, + error::{require, Error, Result}, + secrets_manager::state::{self, ClientState}, Client, }; -pub(crate) async fn access_token_login( +pub(crate) async fn login_access_token( client: &mut Client, input: &AccessTokenLoginRequest, ) -> Result { //info!("api key logging in"); //debug!("{:#?}, {:#?}", client, input); - let access_token = AccessToken::from_str(&input.access_token)?; + let access_token: AccessToken = input.access_token.parse()?; + + if let Some(state_file) = &input.state_file { + if let Ok(organization_id) = load_tokens_from_state(client, state_file, &access_token) { + client.set_login_method(LoginMethod::ServiceAccount( + ServiceAccountLoginMethod::AccessToken { + access_token, + organization_id, + state_file: Some(state_file.to_path_buf()), + }, + )); + + return Ok(AccessTokenLoginResponse { + authenticated: true, + reset_master_password: false, + force_password_reset: false, + two_factor: None, + }); + } + } let response = request_access_token(client, &access_token).await?; if let IdentityTokenResponse::Payload(r) = &response { // Extract the encrypted payload and use the access token encryption key to decrypt it - let payload = EncString::from_str(&r.encrypted_payload)?; + let payload: EncString = r.encrypted_payload.parse()?; - let decrypted_payload = payload.decrypt_with_key(&access_token.encryption_key)?; + let decrypted_payload: Vec = payload.decrypt_with_key(&access_token.encryption_key)?; // Once decrypted, we have to JSON decode to extract the organization encryption key #[derive(serde::Deserialize)] struct Payload { #[serde(rename = "encryptionKey")] - encryption_key: String, + encryption_key: SensitiveString, } let payload: Payload = serde_json::from_slice(&decrypted_payload)?; + let encryption_key = payload.encryption_key.clone().decode_base64(STANDARD)?; + let encryption_key = SymmetricCryptoKey::try_from(encryption_key)?; - let encryption_key = BASE64_ENGINE.decode(payload.encryption_key)?; - - let encryption_key = SymmetricCryptoKey::try_from(encryption_key.as_slice())?; - - let access_token_obj = decode_token(&r.access_token)?; + let access_token_obj: JWTToken = r.access_token.parse()?; // This should always be Some() when logging in with an access token - let organization_id = access_token_obj - .organization - .ok_or(Error::MissingFields)? + let organization_id = require!(access_token_obj.organization) .parse() .map_err(|_| Error::InvalidResponse)?; + if let Some(state_file) = &input.state_file { + let state = ClientState::new(r.access_token.clone(), payload.encryption_key); + _ = state::set(state_file, &access_token, state); + } + client.set_tokens( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, - LoginMethod::ServiceAccount(ServiceAccountLoginMethod::AccessToken { - service_account_id: access_token.service_account_id, - client_secret: access_token.client_secret, - organization_id, - }), ); + 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); } @@ -77,17 +102,46 @@ async fn request_access_token( input: &AccessToken, ) -> Result { let config = client.get_api_configurations().await; - AccessTokenRequest::new(input.service_account_id, &input.client_secret) + AccessTokenRequest::new(input.access_token_id, &input.client_secret) .send(config) .await } +fn load_tokens_from_state( + client: &mut Client, + state_file: &Path, + access_token: &AccessToken, +) -> Result { + let client_state = state::get(state_file, access_token)?; + + let token: JWTToken = client_state.token.parse()?; + + if let Some(organization_id) = token.organization { + let time_till_expiration = (token.exp as i64) - Utc::now().timestamp(); + + if time_till_expiration > 0 { + let organization_id: Uuid = organization_id + .parse() + .map_err(|_| "Bad organization id.")?; + let encryption_key = SymmetricCryptoKey::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); + + return Ok(organization_id); + } + } + + Err(Error::InvalidStateFile) +} + /// Login to Bitwarden with access token #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AccessTokenLoginRequest { /// Bitwarden service API access token pub access_token: String, + pub state_file: Option, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] diff --git a/crates/bitwarden/src/auth/login/api_key.rs b/crates/bitwarden/src/auth/login/api_key.rs index a9fa954d7..5d7fdcd96 100644 --- a/crates/bitwarden/src/auth/login/api_key.rs +++ b/crates/bitwarden/src/auth/login/api_key.rs @@ -1,5 +1,4 @@ -use std::str::FromStr; - +use bitwarden_crypto::EncString; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -7,15 +6,14 @@ use crate::{ auth::{ api::{request::ApiTokenRequest, response::IdentityTokenResponse}, login::{response::two_factor::TwoFactorProviders, PasswordLoginResponse}, + JWTToken, }, client::{LoginMethod, UserLoginMethod}, - crypto::EncString, - error::{Error, Result}, - util::decode_token, + error::{require, Result}, Client, }; -pub(crate) async fn api_key_login( +pub(crate) async fn login_api_key( client: &mut Client, input: &ApiKeyLoginRequest, ) -> Result { @@ -25,29 +23,29 @@ pub(crate) async fn api_key_login( let response = request_api_identity_tokens(client, input).await?; if let IdentityTokenResponse::Authenticated(r) = &response { - let access_token_obj = decode_token(&r.access_token)?; + let access_token_obj: JWTToken = r.access_token.parse()?; // This should always be Some() when logging in with an api key let email = access_token_obj .email - .ok_or(Error::Internal("Access token doesn't contain email"))?; + .ok_or("Access token doesn't contain email")?; - let kdf = client.prelogin(email.clone()).await?; + let kdf = client.auth().prelogin(email.clone()).await?; client.set_tokens( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, - LoginMethod::User(UserLoginMethod::ApiKey { - client_id: input.client_id.to_owned(), - client_secret: input.client_secret.to_owned(), - email, - kdf, - }), ); - - let user_key = EncString::from_str(r.key.as_deref().unwrap()).unwrap(); - let private_key = EncString::from_str(r.private_key.as_deref().unwrap()).unwrap(); + client.set_login_method(LoginMethod::User(UserLoginMethod::ApiKey { + client_id: input.client_id.to_owned(), + client_secret: input.client_secret.to_owned(), + email, + kdf, + })); + + let user_key: EncString = require!(r.key.as_deref()).parse()?; + let private_key: EncString = require!(r.private_key.as_deref()).parse()?; client.initialize_user_crypto(&input.password, user_key, private_key)?; } diff --git a/crates/bitwarden/src/auth/login/auth_request.rs b/crates/bitwarden/src/auth/login/auth_request.rs new file mode 100644 index 000000000..fb449e5d4 --- /dev/null +++ b/crates/bitwarden/src/auth/login/auth_request.rs @@ -0,0 +1,127 @@ +use bitwarden_api_api::{ + apis::auth_requests_api::{auth_requests_id_response_get, auth_requests_post}, + models::{AuthRequestCreateRequestModel, AuthRequestType}, +}; +use bitwarden_crypto::Kdf; +use uuid::Uuid; + +use crate::{ + auth::{ + api::{request::AuthRequestTokenRequest, response::IdentityTokenResponse}, + auth_request::new_auth_request, + }, + client::{LoginMethod, UserLoginMethod}, + error::{require, Result}, + mobile::crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest}, + Client, +}; + +pub struct NewAuthRequestResponse { + pub fingerprint: String, + email: String, + device_identifier: String, + auth_request_id: Uuid, + access_code: String, + private_key: String, +} + +pub(crate) async fn send_new_auth_request( + client: &mut Client, + email: String, + device_identifier: String, +) -> Result { + let config = client.get_api_configurations().await; + + let auth = new_auth_request(&email)?; + + let req = AuthRequestCreateRequestModel { + email: email.clone(), + public_key: auth.public_key, + device_identifier: device_identifier.clone(), + access_code: auth.access_code.clone(), + r#type: AuthRequestType::AuthenticateAndUnlock, + }; + + let res = auth_requests_post(&config.api, Some(req)).await?; + + Ok(NewAuthRequestResponse { + fingerprint: auth.fingerprint, + email, + device_identifier, + auth_request_id: require!(res.id), + access_code: auth.access_code, + private_key: auth.private_key, + }) +} + +pub(crate) async fn complete_auth_request( + client: &mut Client, + auth_req: NewAuthRequestResponse, +) -> Result<()> { + let config = client.get_api_configurations().await; + + let res = auth_requests_id_response_get( + &config.api, + auth_req.auth_request_id, + Some(&auth_req.access_code), + ) + .await?; + + let approved = res.request_approved.unwrap_or(false); + + if !approved { + return Err("Auth request was not approved".into()); + } + + let response = AuthRequestTokenRequest::new( + &auth_req.email, + &auth_req.auth_request_id, + &auth_req.access_code, + config.device_type, + &auth_req.device_identifier, + ) + .send(config) + .await?; + + if let IdentityTokenResponse::Authenticated(r) = response { + let kdf = Kdf::default(); + + client.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(), + })); + + let method = match res.master_password_hash { + Some(_) => AuthRequestMethod::MasterKey { + protected_master_key: require!(res.key).parse()?, + auth_request_key: require!(r.key).parse()?, + }, + None => AuthRequestMethod::UserKey { + protected_user_key: require!(res.key).parse()?, + }, + }; + + client + .crypto() + .initialize_user_crypto(InitUserCryptoRequest { + kdf_params: kdf, + email: auth_req.email, + private_key: require!(r.private_key), + method: InitUserCryptoMethod::AuthRequest { + request_private_key: auth_req.private_key, + method, + }, + }) + .await?; + + Ok(()) + } else { + Err("Failed to authenticate".into()) + } +} diff --git a/crates/bitwarden/src/auth/login/mod.rs b/crates/bitwarden/src/auth/login/mod.rs index 9e1dbb818..745e0b78c 100644 --- a/crates/bitwarden/src/auth/login/mod.rs +++ b/crates/bitwarden/src/auth/login/mod.rs @@ -1,9 +1,6 @@ #[cfg(feature = "internal")] use { - crate::{ - client::{kdf::Kdf, Client}, - error::Result, - }, + crate::{client::Kdf, error::Result, Client}, bitwarden_api_identity::{ apis::accounts_api::accounts_prelogin_post, models::{PreloginRequestModel, PreloginResponseModel}, @@ -14,7 +11,7 @@ pub mod response; mod password; #[cfg(feature = "internal")] -pub(crate) use password::password_login; +pub(crate) use password::login_password; #[cfg(feature = "internal")] pub use password::PasswordLoginRequest; pub use password::PasswordLoginResponse; @@ -28,25 +25,24 @@ pub use two_factor::{TwoFactorEmailRequest, TwoFactorProvider, TwoFactorRequest} #[cfg(feature = "internal")] mod api_key; #[cfg(feature = "internal")] -pub(crate) use api_key::api_key_login; +pub(crate) use api_key::login_api_key; #[cfg(feature = "internal")] pub use api_key::{ApiKeyLoginRequest, ApiKeyLoginResponse}; +#[cfg(feature = "mobile")] +mod auth_request; +#[cfg(feature = "mobile")] +pub use auth_request::NewAuthRequestResponse; +#[cfg(feature = "mobile")] +pub(crate) use auth_request::{complete_auth_request, send_new_auth_request}; + #[cfg(feature = "secrets")] mod access_token; #[cfg(feature = "secrets")] -pub(crate) use access_token::access_token_login; +pub(super) use access_token::login_access_token; #[cfg(feature = "secrets")] pub use access_token::{AccessTokenLoginRequest, AccessTokenLoginResponse}; -#[cfg(feature = "internal")] -async fn determine_password_hash(email: &str, kdf: &Kdf, password: &str) -> Result { - use crate::crypto::{HashPurpose, MasterKey}; - - let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; - master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) -} - #[cfg(feature = "internal")] pub(crate) async fn request_prelogin( client: &mut Client, @@ -56,3 +52,39 @@ pub(crate) async fn request_prelogin( let config = client.get_api_configurations().await; Ok(accounts_prelogin_post(&config.identity, Some(request_model)).await?) } + +#[cfg(feature = "internal")] +pub(crate) fn parse_prelogin(response: PreloginResponseModel) -> Result { + use std::num::NonZeroU32; + + use bitwarden_api_identity::models::KdfType; + use bitwarden_crypto::{ + default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, + default_pbkdf2_iterations, + }; + + let kdf = response.kdf.ok_or("KDF not found")?; + + Ok(match kdf { + KdfType::PBKDF2_SHA256 => Kdf::PBKDF2 { + iterations: response + .kdf_iterations + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_pbkdf2_iterations), + }, + KdfType::Argon2id => Kdf::Argon2id { + iterations: response + .kdf_iterations + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_argon2_iterations), + memory: response + .kdf_memory + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_argon2_memory), + parallelism: response + .kdf_parallelism + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_argon2_parallelism), + }, + }) +} diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs index 33f7ea338..8ae9daebc 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -1,6 +1,3 @@ -#[cfg(feature = "internal")] -use std::str::FromStr; - #[cfg(feature = "internal")] use log::{debug, info}; use schemars::JsonSchema; @@ -8,12 +5,8 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "internal")] use crate::{ - auth::{ - api::request::PasswordTokenRequest, - login::{determine_password_hash, TwoFactorRequest}, - }, - client::{kdf::Kdf, LoginMethod}, - crypto::EncString, + auth::{api::request::PasswordTokenRequest, login::TwoFactorRequest}, + client::{Kdf, LoginMethod}, Client, }; use crate::{ @@ -25,16 +18,23 @@ use crate::{ }; #[cfg(feature = "internal")] -pub(crate) async fn password_login( +pub(crate) async fn login_password( client: &mut Client, input: &PasswordLoginRequest, ) -> Result { - use crate::client::UserLoginMethod; + use bitwarden_crypto::{EncString, HashPurpose}; + + use crate::{auth::determine_password_hash, client::UserLoginMethod, error::require}; info!("password logging in"); debug!("{:#?}, {:#?}", client, input); - let password_hash = determine_password_hash(&input.email, &input.kdf, &input.password).await?; + let password_hash = determine_password_hash( + &input.email, + &input.kdf, + &input.password, + HashPurpose::ServerAuthorization, + )?; let response = request_identity_tokens(client, input, &password_hash).await?; if let IdentityTokenResponse::Authenticated(r) = &response { @@ -42,15 +42,15 @@ pub(crate) async fn password_login( r.access_token.clone(), r.refresh_token.clone(), r.expires_in, - LoginMethod::User(UserLoginMethod::Username { - client_id: "web".to_owned(), - email: input.email.to_owned(), - kdf: input.kdf.to_owned(), - }), ); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "web".to_owned(), + email: input.email.to_owned(), + kdf: input.kdf.to_owned(), + })); - let user_key = EncString::from_str(r.key.as_deref().unwrap()).unwrap(); - let private_key = EncString::from_str(r.private_key.as_deref().unwrap()).unwrap(); + 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(&input.password, user_key, private_key)?; } @@ -62,12 +62,20 @@ pub(crate) async fn password_login( async fn request_identity_tokens( client: &mut Client, input: &PasswordLoginRequest, - password_hash: &String, + password_hash: &str, ) -> Result { + use crate::client::client_settings::DeviceType; + let config = client.get_api_configurations().await; - PasswordTokenRequest::new(&input.email, password_hash, &input.two_factor) - .send(config) - .await + PasswordTokenRequest::new( + &input.email, + password_hash, + DeviceType::ChromeBrowser, + "b86dd6ab-4265-4ddf-a7f1-eb28d5677f33", + &input.two_factor, + ) + .send(config) + .await } #[cfg(feature = "internal")] @@ -93,9 +101,11 @@ pub struct PasswordLoginResponse { pub reset_master_password: bool, /// Whether or not the user is required to update their master password pub force_password_reset: bool, - /// The available two factor authentication options. Present only when authentication fails due to requiring a second authentication factor. + /// The available two factor authentication options. Present only when authentication fails due + /// to requiring a second authentication factor. pub two_factor: Option, - /// The information required to present the user with a captcha challenge. Only present when authentication fails due to requiring validation of a captcha challenge. + /// The information required to present the user with a captcha challenge. Only present when + /// authentication fails due to requiring validation of a captcha challenge. pub captcha: Option, } diff --git a/crates/bitwarden/src/auth/login/two_factor.rs b/crates/bitwarden/src/auth/login/two_factor.rs index 04c411349..c8f0cc55b 100644 --- a/crates/bitwarden/src/auth/login/two_factor.rs +++ b/crates/bitwarden/src/auth/login/two_factor.rs @@ -1,10 +1,10 @@ use bitwarden_api_api::models::TwoFactorEmailRequestModel; +use bitwarden_crypto::HashPurpose; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use super::determine_password_hash; -use crate::{error::Result, Client}; +use crate::{auth::determine_password_hash, error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -20,9 +20,14 @@ pub(crate) async fn send_two_factor_email( input: &TwoFactorEmailRequest, ) -> Result<()> { // TODO: This should be resolved from the client - let kdf = client.prelogin(input.email.clone()).await?; + let kdf = client.auth().prelogin(input.email.clone()).await?; - let password_hash = determine_password_hash(&input.email, &kdf, &input.password).await?; + let password_hash = determine_password_hash( + &input.email, + &kdf, + &input.password, + HashPurpose::ServerAuthorization, + )?; let config = client.get_api_configurations().await; bitwarden_api_api::apis::two_factor_api::two_factor_send_email_login_post( diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden/src/auth/mod.rs index f2f6a3144..b1fe2dbda 100644 --- a/crates/bitwarden/src/auth/mod.rs +++ b/crates/bitwarden/src/auth/mod.rs @@ -1,12 +1,64 @@ +mod access_token; pub(super) mod api; -#[cfg(feature = "internal")] pub mod client_auth; +mod jwt_token; pub mod login; #[cfg(feature = "internal")] pub mod password; 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 = "mobile")] +pub(crate) use auth_request::{auth_request_decrypt_master_key, auth_request_decrypt_user_key}; +#[cfg(feature = "internal")] +mod tde; +#[cfg(feature = "internal")] +pub use tde::RegisterTdeKeyResponse; + +#[cfg(feature = "internal")] +use crate::{client::Kdf, error::Result}; + +#[cfg(feature = "internal")] +fn determine_password_hash( + email: &str, + kdf: &Kdf, + password: &str, + purpose: HashPurpose, +) -> Result { + let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; + Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use super::*; + + #[cfg(feature = "internal")] + #[test] + fn test_determine_password_hash() { + use super::determine_password_hash; + + let password = "password123"; + let email = "test@bitwarden.com"; + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(100_000).unwrap(), + }; + let purpose = HashPurpose::LocalAuthorization; + + let result = determine_password_hash(email, &kdf, password, purpose).unwrap(); + + assert_eq!(result, "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU="); + } +} diff --git a/crates/bitwarden/src/auth/password.rs b/crates/bitwarden/src/auth/password.rs deleted file mode 100644 index a38c75ab6..000000000 --- a/crates/bitwarden/src/auth/password.rs +++ /dev/null @@ -1,34 +0,0 @@ -use schemars::JsonSchema; - -pub(super) fn password_strength( - _password: String, - _email: String, - _additional_inputs: Vec, -) -> u8 { - 2 -} - -pub(super) fn satisfies_policy( - _password: String, - _strength: u8, - _policy: &MasterPasswordPolicyOptions, -) -> bool { - true -} - -#[derive(Debug, JsonSchema)] -#[cfg_attr(feature = "mobile", derive(uniffi::Record))] -#[allow(dead_code)] -pub struct MasterPasswordPolicyOptions { - min_complexity: u8, - min_length: u8, - require_upper: bool, - require_lower: bool, - require_numbers: bool, - require_special: bool, - - /// Flag to indicate if the policy should be enforced on login. - /// If true, and the user's password does not meet the policy requirements, - /// the user will be forced to update their password. - enforce_on_login: bool, -} diff --git a/crates/bitwarden/src/auth/password/mod.rs b/crates/bitwarden/src/auth/password/mod.rs new file mode 100644 index 000000000..d0f3329f2 --- /dev/null +++ b/crates/bitwarden/src/auth/password/mod.rs @@ -0,0 +1,9 @@ +mod policy; +pub(crate) use policy::satisfies_policy; +pub use policy::MasterPasswordPolicyOptions; +mod validate; +pub(crate) use validate::validate_password; +#[cfg(feature = "internal")] +pub(crate) use validate::validate_password_user_key; +mod strength; +pub(crate) use strength::password_strength; diff --git a/crates/bitwarden/src/auth/password/policy.rs b/crates/bitwarden/src/auth/password/policy.rs new file mode 100644 index 000000000..dfd0e770d --- /dev/null +++ b/crates/bitwarden/src/auth/password/policy.rs @@ -0,0 +1,176 @@ +use schemars::JsonSchema; + +/// Validate the provided password passes the provided Master Password Requirements Policy. +pub(crate) fn satisfies_policy( + password: String, + strength: u8, + policy: &MasterPasswordPolicyOptions, +) -> bool { + if policy.min_complexity > 0 && policy.min_complexity > strength { + return false; + } + + if policy.min_length > 0 && usize::from(policy.min_length) > password.len() { + return false; + } + + if policy.require_upper && password.to_lowercase() == password { + return false; + } + + if policy.require_lower && password.to_uppercase() == password { + return false; + } + + if policy.require_numbers && !password.chars().any(|c| c.is_numeric()) { + return false; + } + + if policy.require_special && !password.chars().any(|c| "!@#$%^&*".contains(c)) { + return false; + } + + true +} + +#[derive(Debug, JsonSchema)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +#[allow(dead_code)] +pub struct MasterPasswordPolicyOptions { + min_complexity: u8, + min_length: u8, + require_upper: bool, + require_lower: bool, + require_numbers: bool, + require_special: bool, + + /// Flag to indicate if the policy should be enforced on login. + /// If true, and the user's password does not meet the policy requirements, + /// the user will be forced to update their password. + enforce_on_login: bool, +} + +#[cfg(test)] +mod tests { + + use super::{satisfies_policy, MasterPasswordPolicyOptions}; + + #[test] + fn satisfies_policy_gives_success() { + let password = "lkasfo!icbb$2323ALKJCO22".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 5, + require_upper: true, + require_lower: true, + require_numbers: true, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 4, &options); + assert!(result); + } + + #[test] + fn satisfies_policy_evaluates_strength() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 3, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_length() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 20, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_upper() { + let password = "password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: true, + require_lower: false, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_lower() { + let password = "ABCDEFG123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: true, + require_numbers: false, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_numbers() { + let password = "password".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: true, + require_special: false, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } + + #[test] + fn satisfies_policy_evaluates_special() { + let password = "Password123".to_string(); + let options = MasterPasswordPolicyOptions { + min_complexity: 0, + min_length: 0, + require_upper: false, + require_lower: false, + require_numbers: false, + require_special: true, + enforce_on_login: false, + }; + + let result = satisfies_policy(password, 0, &options); + assert!(!result); + } +} diff --git a/crates/bitwarden/src/auth/password/strength.rs b/crates/bitwarden/src/auth/password/strength.rs new file mode 100644 index 000000000..66b5b5823 --- /dev/null +++ b/crates/bitwarden/src/auth/password/strength.rs @@ -0,0 +1,78 @@ +use zxcvbn::zxcvbn; + +const GLOBAL_INPUTS: [&str; 3] = ["bitwarden", "bit", "warden"]; + +pub(crate) fn password_strength( + password: String, + email: String, + additional_inputs: Vec, +) -> u8 { + let mut inputs = email_to_user_inputs(&email); + inputs.extend(additional_inputs); + + let mut arr: Vec<_> = inputs.iter().map(String::as_str).collect(); + arr.extend(GLOBAL_INPUTS); + + zxcvbn(&password, &arr).map_or(0, |e| e.score()) +} + +fn email_to_user_inputs(email: &str) -> Vec { + let parts = email.split_once('@'); + match parts { + Some((prefix, _)) => prefix + .trim() + .to_lowercase() + .split(|c: char| !c.is_alphanumeric()) + .map(str::to_owned) + .collect(), + None => vec![], + } +} + +#[cfg(test)] +mod tests { + use super::{email_to_user_inputs, password_strength}; + + #[test] + fn test_password_strength() { + let cases = vec![ + ("password", "random@bitwarden.com", 0), + ("password11", "random@bitwarden.com", 1), + ("Weakpass2", "random@bitwarden.com", 2), + ("GoodPass3!", "random@bitwarden.com", 3), + ("VeryStrong123@#", "random@bitwarden.com", 4), + ]; + + for (password, email, expected) in cases { + let result = password_strength(password.to_owned(), email.to_owned(), vec![]); + assert_eq!(expected, result, "{email}: {password}"); + } + } + + #[test] + fn test_penalize_email() { + let password = "asdfjkhkjwer!"; + + let result = password_strength( + password.to_owned(), + "random@bitwarden.com".to_owned(), + vec![], + ); + assert_eq!(result, 4); + + let result = password_strength( + password.to_owned(), + "asdfjkhkjwer@bitwarden.com".to_owned(), + vec![], + ); + assert_eq!(result, 1); + } + + #[test] + fn test_email_to_user_inputs() { + let email = "random@bitwarden.com"; + let result = email_to_user_inputs(email); + + assert_eq!(result, vec!["random"]); + } +} diff --git a/crates/bitwarden/src/auth/password/validate.rs b/crates/bitwarden/src/auth/password/validate.rs new file mode 100644 index 000000000..9003347d9 --- /dev/null +++ b/crates/bitwarden/src/auth/password/validate.rs @@ -0,0 +1,165 @@ +use bitwarden_crypto::{HashPurpose, MasterKey}; + +use crate::{ + auth::determine_password_hash, + client::{LoginMethod, UserLoginMethod}, + error::{Error, Result}, + Client, +}; + +/// Validate if the provided password matches the password hash stored in the client. +pub(crate) fn validate_password( + client: &Client, + password: String, + password_hash: String, +) -> Result { + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + if let LoginMethod::User(login_method) = login_method { + match login_method { + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. } => { + let hash = determine_password_hash( + email, + kdf, + &password, + HashPurpose::LocalAuthorization, + )?; + + Ok(hash == password_hash) + } + } + } else { + Err(Error::NotAuthenticated) + } +} + +#[cfg(feature = "internal")] +pub(crate) fn validate_password_user_key( + client: &Client, + password: String, + encrypted_user_key: String, +) -> Result { + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + if let LoginMethod::User(login_method) = login_method { + match login_method { + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. } => { + let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), 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 existing_key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + + if user_key.to_vec() != existing_key.to_vec() { + return Err("wrong user key".into()); + } + + Ok(master_key + .derive_master_key_hash(password.as_bytes(), HashPurpose::LocalAuthorization)?) + } + } + } else { + Err(Error::NotAuthenticated) + } +} + +#[cfg(test)] +mod tests { + 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}; + + let mut client = Client::new(None); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + email: "test@bitwarden.com".to_string(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(100_000).unwrap(), + }, + client_id: "1".to_string(), + })); + + let password = "password123".to_string(); + let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU=".to_string(); + + let result = validate_password(&client, password, password_hash); + + assert!(result.unwrap()); + } + + #[cfg(feature = "internal")] + #[test] + fn test_validate_password_user_key() { + use std::num::NonZeroU32; + + use crate::client::{Client, Kdf, 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(600_000).unwrap(), + }, + client_id: "1".to_string(), + })); + + 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 + .initialize_user_crypto("asdfasdfasdf", user_key.parse().unwrap(), private_key) + .unwrap(); + + let result = + validate_password_user_key(&client, "asdfasdfasdf".to_string(), user_key.to_string()) + .unwrap(); + + assert_eq!(result, "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA="); + assert!(validate_password(&client, "asdfasdfasdf".to_string(), result.to_string()).unwrap()) + } + + #[cfg(feature = "internal")] + #[test] + fn test_validate_password_user_key_wrong_password() { + use std::num::NonZeroU32; + + use crate::client::{Client, Kdf, 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(600_000).unwrap(), + }, + client_id: "1".to_string(), + })); + + 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 + .initialize_user_crypto("asdfasdfasdf", user_key.parse().unwrap(), private_key) + .unwrap(); + + let result = validate_password_user_key(&client, "abc".to_string(), user_key.to_string()) + .unwrap_err(); + + assert_eq!(result.to_string(), "Internal error: wrong password"); + } +} diff --git a/crates/bitwarden/src/auth/register.rs b/crates/bitwarden/src/auth/register.rs index 8336ec9c9..5cedd9c53 100644 --- a/crates/bitwarden/src/auth/register.rs +++ b/crates/bitwarden/src/auth/register.rs @@ -2,16 +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 schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - client::kdf::Kdf, - crypto::{HashPurpose, MasterKey, RsaKeyPair}, - error::Result, - util::default_pbkdf2_iterations, - Client, -}; +use crate::{client::Kdf, error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -26,9 +21,7 @@ pub struct RegisterRequest { pub(super) async fn register(client: &mut Client, req: &RegisterRequest) -> Result<()> { let config = client.get_api_configurations().await; - let kdf = Kdf::PBKDF2 { - iterations: default_pbkdf2_iterations(), - }; + let kdf = Kdf::default(); let keys = make_register_keys(req.email.to_owned(), req.password.to_owned(), kdf)?; @@ -47,7 +40,7 @@ pub(super) async fn register(client: &mut Client, req: &RegisterRequest) -> Resu })), token: None, organization_user_id: None, - kdf: Some(bitwarden_api_identity::models::KdfType::Variant0), + kdf: Some(bitwarden_api_identity::models::KdfType::PBKDF2_SHA256), kdf_iterations: Some(default_pbkdf2_iterations().get() as i32), kdf_memory: None, kdf_parallelism: None, @@ -79,7 +72,7 @@ pub(super) fn make_register_keys( #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct RegisterKeyResponse { - master_password_hash: String, - encrypted_user_key: String, - keys: RsaKeyPair, + pub master_password_hash: String, + pub encrypted_user_key: String, + pub keys: RsaKeyPair, } diff --git a/crates/bitwarden/src/auth/renew.rs b/crates/bitwarden/src/auth/renew.rs index 3b2dbef7d..6f93f7482 100644 --- a/crates/bitwarden/src/auth/renew.rs +++ b/crates/bitwarden/src/auth/renew.rs @@ -1,4 +1,4 @@ -use std::time::{Duration, Instant}; +use chrono::Utc; #[cfg(feature = "internal")] use crate::{auth::api::request::ApiTokenRequest, client::UserLoginMethod}; @@ -6,13 +6,14 @@ use crate::{ auth::api::{request::AccessTokenRequest, response::IdentityTokenResponse}, client::{Client, LoginMethod, ServiceAccountLoginMethod}, error::{Error, Result}, + secrets_manager::state::{self, ClientState}, }; pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { - const TOKEN_RENEW_MARGIN: Duration = Duration::from_secs(5 * 60); + const TOKEN_RENEW_MARGIN_SECONDS: i64 = 5 * 60; - if let (Some(expires), Some(login_method)) = (&client.token_expires_in, &client.login_method) { - if expires > &(Instant::now() + TOKEN_RENEW_MARGIN) { + if let (Some(expires), Some(login_method)) = (&client.token_expires_on, &client.login_method) { + if Utc::now().timestamp() < expires - TOKEN_RENEW_MARGIN_SECONDS { return Ok(()); } @@ -44,26 +45,42 @@ pub(crate) async fn renew_token(client: &mut Client) -> Result<()> { }, LoginMethod::ServiceAccount(s) => match s { ServiceAccountLoginMethod::AccessToken { - service_account_id, - client_secret, + access_token, + state_file, .. } => { - AccessTokenRequest::new(*service_account_id, client_secret) - .send(&client.__api_configurations) - .await? + let result = AccessTokenRequest::new( + access_token.access_token_id, + &access_token.client_secret, + ) + .send(&client.__api_configurations) + .await?; + + if let ( + IdentityTokenResponse::Authenticated(r), + Some(state_file), + Ok(enc_settings), + ) = (&result, state_file, client.get_encryption_settings()) + { + if let Some(enc_key) = enc_settings.get_key(&None) { + let state = + ClientState::new(r.access_token.clone(), enc_key.to_base64()); + _ = state::set(state_file, access_token, state); + } + } + + result } }, }; match res { IdentityTokenResponse::Refreshed(r) => { - let login_method = login_method.to_owned(); - client.set_tokens(r.access_token, r.refresh_token, r.expires_in, login_method); + client.set_tokens(r.access_token, r.refresh_token, r.expires_in); return Ok(()); } IdentityTokenResponse::Authenticated(r) => { - let login_method = login_method.to_owned(); - client.set_tokens(r.access_token, r.refresh_token, r.expires_in, login_method); + client.set_tokens(r.access_token, r.refresh_token, r.expires_in); return Ok(()); } _ => { diff --git a/crates/bitwarden/src/auth/tde.rs b/crates/bitwarden/src/auth/tde.rs new file mode 100644 index 000000000..420448f16 --- /dev/null +++ b/crates/bitwarden/src/auth/tde.rs @@ -0,0 +1,59 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::{ + AsymmetricEncString, AsymmetricPublicCryptoKey, DeviceKey, EncString, Kdf, SymmetricCryptoKey, + TrustDeviceResponse, UserKey, +}; + +use crate::{error::Result, Client}; + +/// This function generates a new user key and key pair, initializes the client's crypto with the +/// 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, + email: String, + org_public_key: String, + remember_device: bool, +) -> Result { + let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(org_public_key)?)?; + + let mut rng = rand::thread_rng(); + + let user_key = UserKey::new(SymmetricCryptoKey::generate(&mut rng)); + let key_pair = user_key.make_key_pair()?; + + let admin_reset = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(user_key.0.to_vec().expose(), &public_key)?; + + let device_key = if remember_device { + Some(DeviceKey::trust_device(&user_key.0)?) + } else { + 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())?; + + Ok(RegisterTdeKeyResponse { + private_key: key_pair.private, + public_key: key_pair.public, + + admin_reset, + device_key, + }) +} + +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct RegisterTdeKeyResponse { + pub private_key: EncString, + pub public_key: String, + + pub admin_reset: AsymmetricEncString, + pub device_key: Option, +} diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index f6eb0c954..2374d6263 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -1,41 +1,44 @@ -use std::time::{Duration, Instant}; +use std::path::PathBuf; #[cfg(feature = "internal")] -use crate::{ - auth::login::{ - api_key_login, password_login, send_two_factor_email, ApiKeyLoginRequest, - ApiKeyLoginResponse, PasswordLoginRequest, PasswordLoginResponse, TwoFactorEmailRequest, - }, - client::kdf::Kdf, - crypto::EncString, - platform::{ - generate_fingerprint, get_user_api_key, sync, FingerprintRequest, FingerprintResponse, - SecretVerificationRequest, SyncRequest, SyncResponse, UserApiKeyResponse, - }, -}; -use reqwest::header::{self}; +pub use bitwarden_crypto::Kdf; +use bitwarden_crypto::SymmetricCryptoKey; +#[cfg(feature = "internal")] +use bitwarden_crypto::{AsymmetricEncString, EncString}; +use chrono::Utc; +use reqwest::header::{self, HeaderValue}; use uuid::Uuid; #[cfg(feature = "secrets")] -use crate::auth::login::{access_token_login, AccessTokenLoginRequest, AccessTokenLoginResponse}; +use crate::auth::login::{AccessTokenLoginRequest, AccessTokenLoginResponse}; use crate::{ - auth::renew::renew_token, + auth::AccessToken, client::{ client_settings::{ClientSettings, DeviceType}, encryption_settings::EncryptionSettings, }, - crypto::SymmetricCryptoKey, error::{Error, Result}, }; +#[cfg(feature = "internal")] +use crate::{ + client::flags::Flags, + platform::{ + get_user_api_key, sync, SecretVerificationRequest, SyncRequest, SyncResponse, + UserApiKeyResponse, + }, +}; #[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, Clone)] +#[derive(Debug)] pub(crate) enum LoginMethod { #[cfg(feature = "internal")] User(UserLoginMethod), @@ -44,7 +47,7 @@ pub(crate) enum LoginMethod { ServiceAccount(ServiceAccountLoginMethod), } -#[derive(Debug, Clone)] +#[derive(Debug)] #[cfg(feature = "internal")] pub(crate) enum UserLoginMethod { Username { @@ -61,12 +64,12 @@ pub(crate) enum UserLoginMethod { }, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub(crate) enum ServiceAccountLoginMethod { AccessToken { - service_account_id: Uuid, - client_secret: String, + access_token: AccessToken, organization_id: Uuid, + state_file: Option, }, } @@ -74,9 +77,12 @@ pub(crate) enum ServiceAccountLoginMethod { pub struct Client { token: Option, pub(crate) refresh_token: Option, - pub(crate) token_expires_in: Option, + pub(crate) token_expires_on: Option, pub(crate) login_method: Option, + #[cfg(feature = "internal")] + flags: Flags, + /// Use Client::get_api_configurations() to access this. /// It should only be used directly in renew_token #[doc(hidden)] @@ -89,12 +95,30 @@ impl Client { pub fn new(settings_input: Option) -> Self { let settings = settings_input.unwrap_or_default(); - let headers = header::HeaderMap::new(); + fn new_client_builder() -> reqwest::ClientBuilder { + #[allow(unused_mut)] + let mut client_builder = reqwest::Client::builder(); - let client = reqwest::Client::builder() - .default_headers(headers) - .build() - .unwrap(); + #[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, @@ -119,53 +143,49 @@ impl Client { Self { token: None, refresh_token: None, - token_expires_in: 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, } } - 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.renew_token().await.ok(); - &self.__api_configurations - } - #[cfg(feature = "internal")] - pub async fn prelogin(&mut self, email: String) -> Result { - use crate::auth::login::request_prelogin; + pub fn load_flags(&mut self, flags: std::collections::HashMap) { + self.flags = Flags::load_from_map(flags); + } - request_prelogin(self, email).await?.try_into() + #[cfg(feature = "mobile")] + pub(crate) fn get_flags(&self) -> &Flags { + &self.flags } - #[cfg(feature = "internal")] - pub async fn password_login( - &mut self, - input: &PasswordLoginRequest, - ) -> Result { - password_login(self, input).await + 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 async fn api_key_login( - &mut self, - input: &ApiKeyLoginRequest, - ) -> Result { - api_key_login(self, input).await + #[cfg(feature = "mobile")] + pub(crate) fn get_http_client(&self) -> &reqwest::Client { + &self.__api_configurations.external_client } #[cfg(feature = "secrets")] + #[deprecated(note = "Use auth().login_access_token() instead")] pub async fn access_token_login( &mut self, input: &AccessTokenLoginRequest, ) -> Result { - access_token_login(self, input).await + self.auth().login_access_token(input).await } #[cfg(feature = "internal")] @@ -200,7 +220,6 @@ impl Client { self.encryption_settings.as_ref().ok_or(Error::VaultLocked) } - #[cfg(feature = "mobile")] pub(crate) fn set_login_method(&mut self, login_method: LoginMethod) { use log::debug; @@ -213,20 +232,14 @@ impl Client { token: String, refresh_token: Option, expires_in: u64, - login_method: LoginMethod, ) { self.token = Some(token.clone()); self.refresh_token = refresh_token; - self.token_expires_in = Some(Instant::now() + Duration::from_secs(expires_in)); - self.login_method = Some(login_method); + 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); } - pub async fn renew_token(&mut self) -> Result<()> { - renew_token(self).await - } - #[cfg(feature = "internal")] pub fn is_authed(&self) -> bool { self.token.is_some() || self.login_method.is_some() @@ -250,7 +263,47 @@ impl Client { user_key, private_key, )?); - Ok(self.encryption_settings.as_ref().unwrap()) + Ok(self + .encryption_settings + .as_ref() + .expect("Value is initialized previously")) + } + + #[cfg(feature = "internal")] + pub(crate) fn initialize_user_crypto_decrypted_key( + &mut self, + user_key: SymmetricCryptoKey, + private_key: EncString, + ) -> Result<&EncryptionSettings> { + self.encryption_settings = Some(EncryptionSettings::new_decrypted_key( + user_key, + private_key, + )?); + Ok(self + .encryption_settings + .as_ref() + .expect("Value is initialized previously")) + } + + #[cfg(feature = "mobile")] + pub(crate) fn initialize_user_crypto_pin( + &mut self, + pin: &str, + pin_protected_user_key: EncString, + private_key: EncString, + ) -> Result<&EncryptionSettings> { + use bitwarden_crypto::MasterKey; + + let pin_key = match &self.login_method { + Some(LoginMethod::User( + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. }, + )) => MasterKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?, + _ => return Err(Error::NotAuthenticated), + }; + + let decrypted_user_key = pin_key.decrypt_user_key(pin_protected_user_key)?; + self.initialize_user_crypto_decrypted_key(decrypted_user_key, private_key) } pub(crate) fn initialize_crypto_single_key( @@ -258,13 +311,15 @@ impl Client { key: SymmetricCryptoKey, ) -> &EncryptionSettings { self.encryption_settings = Some(EncryptionSettings::new_single_key(key)); - self.encryption_settings.as_ref().unwrap() + self.encryption_settings + .as_ref() + .expect("Value is initialized previously") } #[cfg(feature = "internal")] pub(crate) fn initialize_org_crypto( &mut self, - org_keys: Vec<(Uuid, EncString)>, + org_keys: Vec<(Uuid, AsymmetricEncString)>, ) -> Result<&EncryptionSettings> { let enc = self .encryption_settings @@ -272,111 +327,26 @@ impl Client { .ok_or(Error::VaultLocked)?; enc.set_org_keys(org_keys)?; - Ok(self.encryption_settings.as_ref().unwrap()) - } - - #[cfg(feature = "internal")] - pub fn fingerprint(&mut self, input: &FingerprintRequest) -> Result { - generate_fingerprint(input) - } - - #[cfg(feature = "internal")] - pub async fn send_two_factor_email(&mut self, tf: &TwoFactorEmailRequest) -> Result<()> { - send_two_factor_email(self, tf).await + Ok(&*enc) } } #[cfg(test)] mod tests { - use wiremock::{matchers, Mock, ResponseTemplate}; - - use crate::{auth::login::AccessTokenLoginRequest, secrets_manager::secrets::*}; - - #[tokio::test] - async fn test_access_token_login() { - // Create the mock server with the necessary routes for this test - let (_server, mut client) = crate::util::start_mock(vec![ - Mock::given(matchers::path("/identity/connect/token")) - .respond_with(ResponseTemplate::new(200).set_body_json( - serde_json::json!({ - "access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjMwMURENkE1MEU4NEUxRDA5MUM4MUQzQjAwQkY5MDEwQzg1REJEOUFSUzI1NiIsInR5cCI6\ - ImF0K2p3dCIsIng1dCI6Ik1CM1dwUTZFNGRDUnlCMDdBTC1RRU1oZHZabyJ9.eyJuYmYiOjE2NzUxMDM3ODEsImV4cCI6MTY3NTEwNzM4MSwiaXNzIjo\ - iaHR0cDovL2xvY2FsaG9zdCIsImNsaWVudF9pZCI6ImVjMmMxZDQ2LTZhNGItNDc1MS1hMzEwLWFmOTYwMTMxN2YyZCIsInN1YiI6ImQzNDgwNGNhLTR\ - mNmMtNDM5Mi04NmI3LWFmOTYwMTMxNzVkMCIsIm9yZ2FuaXphdGlvbiI6ImY0ZTQ0YTdmLTExOTAtNDMyYS05ZDRhLWFmOTYwMTMxMjdjYiIsImp0aSI\ - 6IjU3QUU0NzQ0MzIwNzk1RThGQkQ4MUIxNDA2RDQyNTQyIiwiaWF0IjoxNjc1MTAzNzgxLCJzY29wZSI6WyJhcGkuc2VjcmV0cyJdfQ.GRKYzqgJZHEE\ - ZHsJkhVZH8zjYhY3hUvM4rhdV3FU10WlCteZdKHrPIadCUh-Oz9DxIAA2HfALLhj1chL4JgwPmZgPcVS2G8gk8XeBmZXowpVWJ11TXS1gYrM9syXbv9j\ - 0JUCdpeshH7e56WnlpVynyUwIum9hmYGZ_XJUfmGtlKLuNjYnawTwLEeR005uEjxq3qI1kti-WFnw8ciL4a6HLNulgiFw1dAvs4c7J0souShMfrnFO3g\ - SOHff5kKD3hBB9ynDBnJQSFYJ7dFWHIjhqs0Vj-9h0yXXCcHvu7dVGpaiNjNPxbh6YeXnY6UWcmHLDtFYsG2BWcNvVD4-VgGxXt3cMhrn7l3fSYuo32Z\ - Yk4Wop73XuxqF2fmfmBdZqGI1BafhENCcZw_bpPSfK2uHipfztrgYnrzwvzedz0rjFKbhDyrjzuRauX5dqVJ4ntPeT9g_I5n71gLxiP7eClyAx5RxdF6\ - He87NwC8i-hLBhugIvLTiDj-Sk9HvMth6zaD0ebxd56wDjq8-CMG_WcgusDqNzKFHqWNDHBXt8MLeTgZAR2rQMIMFZqFgsJlRflbig8YewmNUA9wAU74\ - TfxLY1foO7Xpg49vceB7C-PlvGi1VtX6F2i0tc_67lA5kWXnnKBPBUyspoIrmAUCwfms5nTTqA9xXAojMhRHAos_OdM", - "expires_in":3600, - "token_type":"Bearer", - "scope":"api.secrets", - "encrypted_payload":"2.E9fE8+M/VWMfhhim1KlCbQ==|eLsHR484S/tJbIkM6spnG/HP65tj9A6Tba7kAAvUp+rYuQmGLixiOCfMsqt5OvBctDfvvr/Aes\ - Bu7cZimPLyOEhqEAjn52jF0eaI38XZfeOG2VJl0LOf60Wkfh3ryAMvfvLj3G4ZCNYU8sNgoC2+IQ==|lNApuCQ4Pyakfo/wwuuajWNaEX/2MW8/3rjXB/V7n+k="}) - )), - Mock::given(matchers::path("/api/organizations/f4e44a7f-1190-432a-9d4a-af96013127cb/secrets")) - .respond_with(ResponseTemplate::new(200).set_body_json( - serde_json::json!({ - "secrets":[{ - "id":"15744a66-341a-4c62-af50-af960166b6bc", - "organizationId":"f4e44a7f-1190-432a-9d4a-af96013127cb", - "key":"2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=", - "creationDate":"2023-01-26T21:46:02.2182556Z", - "revisionDate":"2023-01-26T21:46:02.2182557Z" - }], - "projects":[], - "object":"SecretsWithProjectsList" - }) - )), - Mock::given(matchers::path("/api/secrets/15744a66-341a-4c62-af50-af960166b6bc")) - .respond_with(ResponseTemplate::new(200).set_body_json( - serde_json::json!({ - "id":"15744a66-341a-4c62-af50-af960166b6bc", - "organizationId":"f4e44a7f-1190-432a-9d4a-af96013127cb", - "key":"2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=", - "value":"2.Gl34n9JYABC7V21qHcBzHg==|c1Ds244pob7i+8+MXe4++w==|Shimz/qKMYZmzSFWdeBzFb9dFz7oF6Uv9oqkws7rEe0=", - "note":"2.Cn9ABJy7+WfR4uUHwdYepg==|+nbJyU/6hSknoa5dcEJEUg==|1DTp/ZbwGO3L3RN+VMsCHz8XDr8egn/M5iSitGGysPA=", - "creationDate":"2023-01-26T21:46:02.2182556Z", - "revisionDate":"2023-01-26T21:46:02.2182557Z", - "object":"secret" - }) - )) - ]).await; - - // Test the login is correct and we store the returned organization ID correctly - let res = client - .access_token_login(&AccessTokenLoginRequest { - access_token: "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ==".into(), - }) - .await - .unwrap(); - assert!(res.authenticated); - let organization_id = client.get_access_token_organization().unwrap(); - assert_eq!( - organization_id.to_string(), - "f4e44a7f-1190-432a-9d4a-af96013127cb" - ); + #[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. - // Test that we can retrieve the list of secrets correctly - let mut res = client - .secrets() - .list(&SecretIdentifiersRequest { organization_id }) - .await - .unwrap(); - assert_eq!(res.data.len(), 1); - - // Test that given a secret ID we can get it's data - let res = client - .secrets() - .get(&SecretGetRequest { - id: res.data.remove(0).id, - }) - .await + // 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(); - assert_eq!(res.key, "TEST"); - assert_eq!(res.note, "TEST"); - assert_eq!(res.value, "TEST"); } } diff --git a/crates/bitwarden/src/client/client_settings.rs b/crates/bitwarden/src/client/client_settings.rs index 68ef7b3a9..2f0637b4a 100644 --- a/crates/bitwarden/src/client/client_settings.rs +++ b/crates/bitwarden/src/client/client_settings.rs @@ -1,14 +1,13 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// 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. +/// 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. /// /// Defaults to /// /// ``` /// # use bitwarden::client::client_settings::{ClientSettings, DeviceType}; -/// # use assert_matches::assert_matches; /// let settings = ClientSettings { /// identity_url: "https://identity.bitwarden.com".to_string(), /// api_url: "https://api.bitwarden.com".to_string(), @@ -16,10 +15,7 @@ use serde::{Deserialize, Serialize}; /// device_type: DeviceType::SDK, /// }; /// let default = ClientSettings::default(); -/// assert_matches!(settings, default); /// ``` -/// -/// Targets `localhost:8080` for debug builds. #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index 9c79a1781..6e4da9895 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -1,21 +1,16 @@ use std::collections::HashMap; -use rsa::RsaPrivateKey; -use uuid::Uuid; +use bitwarden_crypto::{AsymmetricCryptoKey, KeyContainer, SymmetricCryptoKey}; #[cfg(feature = "internal")] -use { - crate::client::UserLoginMethod, - rsa::{pkcs8::DecodePrivateKey, Oaep}, -}; +use bitwarden_crypto::{AsymmetricEncString, EncString}; +use uuid::Uuid; -use crate::{ - crypto::{encrypt_aes256_hmac, EncString, SymmetricCryptoKey}, - error::{CryptoError, Result}, -}; +#[cfg(feature = "internal")] +use crate::{client::UserLoginMethod, error::Result}; pub struct EncryptionSettings { user_key: SymmetricCryptoKey, - private_key: Option, + pub(crate) private_key: Option, org_keys: HashMap, } @@ -26,6 +21,7 @@ impl std::fmt::Debug for EncryptionSettings { } impl EncryptionSettings { + /// Initialize the encryption settings with the user password and their encrypted keys #[cfg(feature = "internal")] pub(crate) fn new( login_method: &UserLoginMethod, @@ -33,7 +29,7 @@ impl EncryptionSettings { user_key: EncString, private_key: EncString, ) -> Result { - use crate::crypto::MasterKey; + use bitwarden_crypto::MasterKey; match login_method { UserLoginMethod::Username { email, kdf, .. } @@ -44,24 +40,36 @@ impl EncryptionSettings { // Decrypt the user key let user_key = master_key.decrypt_user_key(user_key)?; - // Decrypt the private key with the user key - let private_key = { - let dec = private_key.decrypt_with_key(&user_key)?; - Some( - rsa::RsaPrivateKey::from_pkcs8_der(&dec) - .map_err(|_| CryptoError::InvalidKey)?, - ) - }; - - Ok(EncryptionSettings { - user_key, - private_key, - org_keys: HashMap::new(), - }) + Self::new_decrypted_key(user_key, private_key) } } } + /// Initialize the encryption settings with the decrypted user key and the encrypted user + /// private key This should only be used when unlocking the vault via biometrics or when the + /// vault is set to lock: "never" Otherwise handling the decrypted user key is dangerous and + /// discouraged + #[cfg(feature = "internal")] + pub(crate) fn new_decrypted_key( + user_key: SymmetricCryptoKey, + private_key: EncString, + ) -> Result { + use bitwarden_crypto::KeyDecryptable; + + let private_key = { + let dec: Vec = private_key.decrypt_with_key(&user_key)?; + Some(AsymmetricCryptoKey::from_der(&dec)?) + }; + + Ok(EncryptionSettings { + user_key, + private_key, + org_keys: HashMap::new(), + }) + } + + /// Initialize the encryption settings with only a single decrypted key. + /// This is used only for logging in Secrets Manager with an access token pub(crate) fn new_single_key(key: SymmetricCryptoKey) -> Self { EncryptionSettings { user_key: key, @@ -73,24 +81,23 @@ impl EncryptionSettings { #[cfg(feature = "internal")] pub(crate) fn set_org_keys( &mut self, - org_enc_keys: Vec<(Uuid, EncString)>, + org_enc_keys: Vec<(Uuid, AsymmetricEncString)>, ) -> Result<&mut Self> { + 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(); + // Decrypt the org keys with the private key for (org_id, org_enc_key) in org_enc_keys { - let data = match org_enc_key { - EncString::Rsa2048_OaepSha1_B64 { data } => data, - _ => return Err(CryptoError::InvalidKey.into()), - }; + let mut dec: Vec = org_enc_key.decrypt_with_key(private_key)?; - let dec = private_key - .decrypt(Oaep::new::(), &data) - .map_err(|_| CryptoError::KeyDecrypt)?; - - let org_key = SymmetricCryptoKey::try_from(dec.as_slice())?; + let org_key = SymmetricCryptoKey::try_from(dec.as_mut_slice())?; self.org_keys.insert(org_id, org_key); } @@ -98,8 +105,9 @@ impl EncryptionSettings { Ok(self) } - fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey> { - // If we don't have a private key set (to decode multiple org keys), we just use the main user key + pub(crate) fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey> { + // 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); } @@ -109,43 +117,10 @@ impl EncryptionSettings { None => Some(&self.user_key), } } - - pub(crate) fn decrypt_bytes( - &self, - cipher: &EncString, - org_id: &Option, - ) -> Result> { - let key = self.get_key(org_id).ok_or(CryptoError::NoKeyForOrg)?; - cipher.decrypt_with_key(key) - } - - pub(crate) fn decrypt(&self, cipher: &EncString, org_id: &Option) -> Result { - let dec = self.decrypt_bytes(cipher, org_id)?; - String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into()) - } - - pub(crate) fn encrypt(&self, data: &[u8], org_id: &Option) -> Result { - let key = self.get_key(org_id).ok_or(CryptoError::NoKeyForOrg)?; - - let dec = encrypt_aes256_hmac(data, key.mac_key.ok_or(CryptoError::InvalidMac)?, key.key)?; - Ok(dec) - } } -#[cfg(test)] -mod tests { - use super::{EncryptionSettings, SymmetricCryptoKey}; - use crate::crypto::{Decryptable, Encryptable}; - - #[test] - fn test_encryption_settings() { - let key = SymmetricCryptoKey::generate("test"); - let settings = EncryptionSettings::new_single_key(key); - - let test_string = "encrypted_test_string".to_string(); - let cipher = test_string.clone().encrypt(&settings, &None).unwrap(); - - let decrypted_str = cipher.decrypt(&settings, &None).unwrap(); - assert_eq!(decrypted_str, test_string); +impl KeyContainer for EncryptionSettings { + fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey> { + EncryptionSettings::get_key(self, org_id) } } diff --git a/crates/bitwarden/src/client/flags.rs b/crates/bitwarden/src/client/flags.rs new file mode 100644 index 000000000..0fc17534b --- /dev/null +++ b/crates/bitwarden/src/client/flags.rs @@ -0,0 +1,43 @@ +#[derive(Debug, Default, Clone, serde::Deserialize)] +pub struct Flags { + #[serde(default, rename = "enableCipherKeyEncryption")] + pub enable_cipher_key_encryption: bool, +} + +impl Flags { + pub fn load_from_map(map: std::collections::HashMap) -> Self { + let map = map + .into_iter() + .map(|(k, v)| (k, serde_json::Value::Bool(v))) + .collect(); + serde_json::from_value(serde_json::Value::Object(map)).expect("Valid map") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_load_empty_map() { + let map = std::collections::HashMap::new(); + let flags = Flags::load_from_map(map); + assert!(!flags.enable_cipher_key_encryption); + } + + #[test] + fn test_load_valid_map() { + let mut map = std::collections::HashMap::new(); + map.insert("enableCipherKeyEncryption".into(), true); + let flags = Flags::load_from_map(map); + assert!(flags.enable_cipher_key_encryption); + } + + #[test] + fn test_load_invalid_map() { + let mut map = std::collections::HashMap::new(); + map.insert("thisIsNotAFlag".into(), true); + let flags = Flags::load_from_map(map); + assert!(!flags.enable_cipher_key_encryption); + } +} diff --git a/crates/bitwarden/src/client/kdf.rs b/crates/bitwarden/src/client/kdf.rs deleted file mode 100644 index 4f1edfdb0..000000000 --- a/crates/bitwarden/src/client/kdf.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::num::NonZeroU32; - -#[cfg(feature = "internal")] -use bitwarden_api_identity::models::{KdfType, PreloginResponseModel}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "internal")] -use crate::error::{Error, Result}; - -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] -pub enum Kdf { - PBKDF2 { - iterations: NonZeroU32, - }, - Argon2id { - iterations: NonZeroU32, - memory: NonZeroU32, - parallelism: NonZeroU32, - }, -} - -#[cfg(feature = "internal")] -impl TryFrom for Kdf { - type Error = Error; - - fn try_from(response: PreloginResponseModel) -> Result { - use crate::util::{ - default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, - default_pbkdf2_iterations, - }; - - let kdf = response.kdf.ok_or(Error::Internal("KDF not found"))?; - - Ok(match kdf { - KdfType::Variant0 => Kdf::PBKDF2 { - iterations: response - .kdf_iterations - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_pbkdf2_iterations), - }, - KdfType::Variant1 => Kdf::Argon2id { - iterations: response - .kdf_iterations - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_argon2_iterations), - memory: response - .kdf_memory - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_argon2_memory), - parallelism: response - .kdf_parallelism - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_argon2_parallelism), - }, - }) - } -} diff --git a/crates/bitwarden/src/client/mod.rs b/crates/bitwarden/src/client/mod.rs index 25a2f5db0..0c703570f 100644 --- a/crates/bitwarden/src/client/mod.rs +++ b/crates/bitwarden/src/client/mod.rs @@ -1,12 +1,12 @@ //! Bitwarden SDK Client pub(crate) use client::*; -pub(crate) mod access_token; #[allow(clippy::module_inception)] mod client; pub mod client_settings; pub(crate) mod encryption_settings; -pub mod kdf; -pub use access_token::AccessToken; +#[cfg(feature = "internal")] +mod flags; + pub use client::Client; diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden/src/crypto/aes_ops.rs deleted file mode 100644 index 34d4021e8..000000000 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ /dev/null @@ -1,77 +0,0 @@ -use aes::cipher::{ - block_padding::Pkcs7, generic_array::GenericArray, typenum::U32, BlockDecryptMut, - BlockEncryptMut, KeyIvInit, -}; -use hmac::Mac; -use rand::RngCore; - -use crate::{ - crypto::{EncString, PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, - error::{CryptoError, Result}, -}; - -pub fn decrypt_aes256(iv: &[u8; 16], data: Vec, key: GenericArray) -> Result> { - // Decrypt data - let iv = GenericArray::from_slice(iv); - let mut data = data; - let decrypted_key_slice = cbc::Decryptor::::new(&key, iv) - .decrypt_padded_mut::(&mut data) - .map_err(|_| CryptoError::KeyDecrypt)?; - - //Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, we truncate to the subslice length - let decrypted_len = decrypted_key_slice.len(); - data.truncate(decrypted_len); - - Ok(data) -} - -pub fn decrypt_aes256_hmac( - iv: &[u8; 16], - mac: &[u8; 32], - data: Vec, - mac_key: GenericArray, - key: GenericArray, -) -> Result> { - let res = validate_mac(&mac_key, iv, &data)?; - if res != *mac { - return Err(CryptoError::InvalidMac.into()); - } - decrypt_aes256(iv, data, key) -} - -pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> Result { - let (iv, data) = encrypt_aes256_internal(data_dec, key); - - Ok(EncString::AesCbc256_B64 { iv, data }) -} - -pub fn encrypt_aes256_hmac( - data_dec: &[u8], - mac_key: GenericArray, - key: GenericArray, -) -> Result { - let (iv, data) = encrypt_aes256_internal(data_dec, key); - let mac = validate_mac(&mac_key, &iv, &data)?; - - Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) -} - -fn encrypt_aes256_internal(data_dec: &[u8], key: GenericArray) -> ([u8; 16], Vec) { - let mut iv = [0u8; 16]; - rand::thread_rng().fill_bytes(&mut iv); - let data = cbc::Encryptor::::new(&key, &iv.into()) - .encrypt_padded_vec_mut::(data_dec); - - (iv, data) -} - -fn validate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> { - let mut hmac = PbkdfSha256Hmac::new_from_slice(mac_key).expect("HMAC can take key of any size"); - hmac.update(iv); - hmac.update(data); - let mac: [u8; PBKDF_SHA256_HMAC_OUT_SIZE] = (*hmac.finalize().into_bytes()) - .try_into() - .map_err(|_| CryptoError::InvalidMac)?; - - Ok(mac) -} diff --git a/crates/bitwarden/src/crypto/enc_string.rs b/crates/bitwarden/src/crypto/enc_string.rs deleted file mode 100644 index b701aaf8f..000000000 --- a/crates/bitwarden/src/crypto/enc_string.rs +++ /dev/null @@ -1,381 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use base64::Engine; -use serde::{de::Visitor, Deserialize}; -use uuid::Uuid; - -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{decrypt_aes256_hmac, Decryptable, Encryptable, SymmetricCryptoKey}, - error::{CryptoError, EncStringParseError, Error, Result}, - util::BASE64_ENGINE, -}; - -#[derive(Clone)] -#[allow(unused, non_camel_case_types)] -pub enum EncString { - /// 0 - AesCbc256_B64 { iv: [u8; 16], data: Vec }, - /// 1 - AesCbc128_HmacSha256_B64 { - iv: [u8; 16], - mac: [u8; 32], - data: Vec, - }, - /// 2 - AesCbc256_HmacSha256_B64 { - iv: [u8; 16], - mac: [u8; 32], - data: Vec, - }, - /// 3 - Rsa2048_OaepSha256_B64 { data: Vec }, - /// 4 - Rsa2048_OaepSha1_B64 { data: Vec }, - /// 5 - #[deprecated] - Rsa2048_OaepSha256_HmacSha256_B64 { data: Vec }, - /// 6 - #[deprecated] - Rsa2048_OaepSha1_HmacSha256_B64 { data: Vec }, -} - -// We manually implement these to make sure we don't print any sensitive data -impl std::fmt::Debug for EncString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EncString").finish() - } -} - -impl FromStr for EncString { - type Err = Error; - - fn from_str(s: &str) -> Result { - let (enc_type, parts): (&str, Vec<_>) = { - let header_parts: Vec<_> = s.split('.').collect(); - - if header_parts.len() == 2 { - (header_parts[0], header_parts[1].split('|').collect()) - } else { - // Support legacy format with no header - let parts: Vec<_> = s.split('|').collect(); - if parts.len() == 3 { - ("1", parts) // AesCbc128_HmacSha256_B64 - } else { - ("0", parts) // AesCbc256_B64 - } - } - }; - - fn from_b64_vec(s: &str) -> Result> { - Ok(BASE64_ENGINE - .decode(s) - .map_err(EncStringParseError::InvalidBase64)?) - } - - fn from_b64(s: &str) -> Result<[u8; N]> { - Ok(from_b64_vec(s)?.try_into().map_err(invalid_len_error(N))?) - } - - match (enc_type, parts.len()) { - ("0", 2) => { - let iv = from_b64(parts[0])?; - let data = from_b64_vec(parts[1])?; - - Ok(EncString::AesCbc256_B64 { iv, data }) - } - ("1" | "2", 3) => { - let iv = from_b64(parts[0])?; - let data = from_b64_vec(parts[1])?; - let mac = from_b64(parts[2])?; - - if enc_type == "1" { - Ok(EncString::AesCbc128_HmacSha256_B64 { iv, mac, data }) - } else { - Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) - } - } - ("3", 1) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha256_B64 { data }) - } - ("4", 1) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha1_B64 { data }) - } - #[allow(deprecated)] - ("5", 1) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data }) - } - #[allow(deprecated)] - ("6", 1) => { - let data = from_b64_vec(parts[0])?; - Ok(EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data }) - } - - (enc_type, parts) => Err(EncStringParseError::InvalidType { - enc_type: enc_type.to_string(), - parts, - } - .into()), - } - } -} - -impl EncString { - #[cfg(feature = "mobile")] - pub(crate) fn from_buffer(buf: &[u8]) -> Result { - if buf.is_empty() { - return Err(EncStringParseError::NoType.into()); - } - let enc_type = buf[0]; - - fn check_length(buf: &[u8], expected: usize) -> Result<()> { - if buf.len() < expected { - return Err(EncStringParseError::InvalidLength { - expected, - got: buf.len(), - } - .into()); - } - Ok(()) - } - - match enc_type { - 0 => { - check_length(buf, 18)?; - let iv = buf[1..17].try_into().unwrap(); - let data = buf[17..].to_vec(); - - Ok(EncString::AesCbc256_B64 { iv, data }) - } - 1 | 2 => { - check_length(buf, 50)?; - let iv = buf[1..17].try_into().unwrap(); - let mac = buf[17..49].try_into().unwrap(); - let data = buf[49..].to_vec(); - - if enc_type == 1 { - Ok(EncString::AesCbc128_HmacSha256_B64 { iv, mac, data }) - } else { - Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data }) - } - } - 3 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha256_B64 { data }) - } - 4 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha1_B64 { data }) - } - #[allow(deprecated)] - 5 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data }) - } - #[allow(deprecated)] - 6 => { - check_length(buf, 2)?; - let data = buf[1..].to_vec(); - Ok(EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data }) - } - _ => Err(EncStringParseError::InvalidType { - enc_type: enc_type.to_string(), - parts: 1, - } - .into()), - } - } - - #[cfg(feature = "mobile")] - pub(crate) fn to_buffer(&self) -> Result> { - let mut buf; - - match self { - EncString::AesCbc256_B64 { iv, data } => { - buf = Vec::with_capacity(1 + 16 + data.len()); - buf.push(self.enc_type()); - buf.extend_from_slice(iv); - buf.extend_from_slice(data); - } - EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } - | EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { - buf = Vec::with_capacity(1 + 16 + 32 + data.len()); - buf.push(self.enc_type()); - buf.extend_from_slice(iv); - buf.extend_from_slice(mac); - buf.extend_from_slice(data); - } - - EncString::Rsa2048_OaepSha256_B64 { data } - | EncString::Rsa2048_OaepSha1_B64 { data } => { - buf = Vec::with_capacity(1 + data.len()); - buf.push(self.enc_type()); - buf.extend_from_slice(data); - } - #[allow(deprecated)] - EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data } - | EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data } => { - buf = Vec::with_capacity(1 + data.len()); - buf.push(self.enc_type()); - buf.extend_from_slice(data); - } - } - - Ok(buf) - } -} - -impl Display for EncString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let parts: Vec<&[u8]> = match self { - EncString::AesCbc256_B64 { iv, data } => vec![iv, data], - EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], - EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => vec![iv, data, mac], - EncString::Rsa2048_OaepSha256_B64 { data } => vec![data], - EncString::Rsa2048_OaepSha1_B64 { data } => vec![data], - #[allow(deprecated)] - EncString::Rsa2048_OaepSha256_HmacSha256_B64 { data } => vec![data], - #[allow(deprecated)] - EncString::Rsa2048_OaepSha1_HmacSha256_B64 { data } => vec![data], - }; - - let encoded_parts: Vec = parts - .iter() - .map(|part| BASE64_ENGINE.encode(part)) - .collect(); - - write!(f, "{}.{}", self.enc_type(), encoded_parts.join("|"))?; - - Ok(()) - } -} - -impl<'de> Deserialize<'de> for EncString { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct CSVisitor; - impl Visitor<'_> for CSVisitor { - type Value = EncString; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "a valid string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - EncString::from_str(v).map_err(|e| E::custom(format!("{:?}", e))) - } - } - - deserializer.deserialize_str(CSVisitor) - } -} - -impl serde::Serialize for EncString { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl EncString { - const fn enc_type(&self) -> u8 { - match self { - EncString::AesCbc256_B64 { .. } => 0, - EncString::AesCbc128_HmacSha256_B64 { .. } => 1, - EncString::AesCbc256_HmacSha256_B64 { .. } => 2, - EncString::Rsa2048_OaepSha256_B64 { .. } => 3, - EncString::Rsa2048_OaepSha1_B64 { .. } => 4, - #[allow(deprecated)] - EncString::Rsa2048_OaepSha256_HmacSha256_B64 { .. } => 5, - #[allow(deprecated)] - EncString::Rsa2048_OaepSha1_HmacSha256_B64 { .. } => 6, - } - } - - pub fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { - match self { - EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => { - let mac_key = key.mac_key.ok_or(CryptoError::InvalidMac)?; - let dec = decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, key.key)?; - Ok(dec) - } - _ => Err(CryptoError::InvalidKey.into()), - } - } -} - -fn invalid_len_error(expected: usize) -> impl Fn(Vec) -> EncStringParseError { - move |e: Vec<_>| EncStringParseError::InvalidLength { - expected, - got: e.len(), - } -} - -impl Encryptable for String { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { - enc.encrypt(self.as_bytes(), org_id) - } -} - -impl Decryptable for EncString { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { - enc.decrypt(self, org_id) - } -} - -#[cfg(test)] -mod tests { - use super::EncString; - - #[test] - fn test_enc_string_serialization() { - #[derive(serde::Serialize, serde::Deserialize)] - struct Test { - key: EncString, - } - - let cipher = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8="; - let serialized = format!("{{\"key\":\"{cipher}\"}}"); - - let t = serde_json::from_str::(&serialized).unwrap(); - assert_eq!(t.key.enc_type(), 2); - assert_eq!(t.key.to_string(), cipher); - assert_eq!(serde_json::to_string(&t).unwrap(), serialized); - } - - #[cfg(feature = "mobile")] - #[test] - fn test_enc_from_to_buffer() { - let enc_str: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8="; - let enc_string: EncString = enc_str.parse().unwrap(); - - let enc_buf = enc_string.to_buffer().unwrap(); - - assert_eq!( - enc_buf, - vec![ - 2, 164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150, 67, - 163, 228, 185, 63, 138, 95, 246, 177, 174, 3, 125, 185, 176, 249, 2, 57, 54, 96, - 220, 49, 66, 72, 44, 221, 98, 76, 209, 45, 48, 180, 111, 93, 118, 241, 43, 16, 211, - 135, 233, 150, 136, 221, 71, 140, 125, 141, 215 - ] - ); - - let enc_string_new = EncString::from_buffer(&enc_buf).unwrap(); - - assert_eq!(enc_string_new.to_string(), enc_str) - } -} diff --git a/crates/bitwarden/src/crypto/encryptable.rs b/crates/bitwarden/src/crypto/encryptable.rs deleted file mode 100644 index bd987060f..000000000 --- a/crates/bitwarden/src/crypto/encryptable.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{collections::HashMap, hash::Hash}; - -use uuid::Uuid; - -use crate::{client::encryption_settings::EncryptionSettings, error::Result}; - -pub trait Encryptable { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result; -} - -pub trait Decryptable { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result; -} - -impl, Output> Encryptable> for Option { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result> { - self.map(|e| e.encrypt(enc, org_id)).transpose() - } -} - -impl, Output> Decryptable> for Option { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result> { - self.as_ref().map(|e| e.decrypt(enc, org_id)).transpose() - } -} - -impl, Output> Encryptable> for Vec { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result> { - self.into_iter().map(|e| e.encrypt(enc, org_id)).collect() - } -} - -impl, Output> Decryptable> for Vec { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result> { - self.iter().map(|e| e.decrypt(enc, org_id)).collect() - } -} - -impl, Output, Id: Hash + Eq> Encryptable> - for HashMap -{ - fn encrypt( - self, - enc: &EncryptionSettings, - org_id: &Option, - ) -> Result> { - self.into_iter() - .map(|(id, e)| Ok((id, e.encrypt(enc, org_id)?))) - .collect::>>() - } -} - -impl, Output, Id: Hash + Eq + Copy> Decryptable> - for HashMap -{ - fn decrypt( - &self, - enc: &EncryptionSettings, - org_id: &Option, - ) -> Result> { - self.iter() - .map(|(id, e)| Ok((*id, e.decrypt(enc, org_id)?))) - .collect::>>() - } -} - -impl, Output> Encryptable for Box { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { - (*self).encrypt(enc, org_id) - } -} - -impl, Output> Decryptable for Box { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { - (**self).decrypt(enc, org_id) - } -} diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden/src/crypto/master_key.rs deleted file mode 100644 index f908d5299..000000000 --- a/crates/bitwarden/src/crypto/master_key.rs +++ /dev/null @@ -1,228 +0,0 @@ -use aes::cipher::{generic_array::GenericArray, typenum::U32}; -use base64::Engine; -use rand::Rng; -use sha2::Digest; - -use super::{ - encrypt_aes256, hkdf_expand, EncString, PbkdfSha256Hmac, SymmetricCryptoKey, UserKey, - PBKDF_SHA256_HMAC_OUT_SIZE, -}; -use crate::{client::kdf::Kdf, error::Result, util::BASE64_ENGINE}; - -#[derive(Copy, Clone)] -pub(crate) enum HashPurpose { - ServerAuthorization = 1, - // LocalAuthorization = 2, -} - -/// A Master Key. -pub(crate) struct MasterKey(SymmetricCryptoKey); - -impl MasterKey { - /// Derives a users master key from their password, email and KDF. - pub fn derive(password: &[u8], email: &[u8], kdf: &Kdf) -> Result { - derive_key(password, email, kdf).map(Self) - } - - /// Derive the master key hash, used for server authorization. - pub(crate) fn derive_master_key_hash( - &self, - password: &[u8], - purpose: HashPurpose, - ) -> Result { - let hash = pbkdf2::pbkdf2_array::( - &self.0.key, - password, - purpose as u32, - ) - .expect("hash is a valid fixed size"); - - Ok(BASE64_ENGINE.encode(hash)) - } - - pub(crate) fn make_user_key(&self) -> Result<(UserKey, EncString)> { - let mut user_key = [0u8; 64]; - rand::thread_rng().fill(&mut user_key); - - let protected = encrypt_aes256(&user_key, self.0.key)?; - - let u: &[u8] = &user_key; - Ok((UserKey::new(SymmetricCryptoKey::try_from(u)?), protected)) - } - - pub(crate) fn decrypt_user_key(&self, user_key: EncString) -> Result { - let stretched_key = stretch_master_key(self)?; - - let dec = user_key.decrypt_with_key(&stretched_key)?; - SymmetricCryptoKey::try_from(dec.as_slice()) - } -} - -/// Derive a generic key from a secret and salt using the provided KDF. -fn derive_key(secret: &[u8], salt: &[u8], kdf: &Kdf) -> Result { - let hash = match kdf { - Kdf::PBKDF2 { iterations } => pbkdf2::pbkdf2_array::< - PbkdfSha256Hmac, - PBKDF_SHA256_HMAC_OUT_SIZE, - >(secret, salt, iterations.get()) - .unwrap(), - - Kdf::Argon2id { - iterations, - memory, - parallelism, - } => { - 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), - ) - .unwrap(), - ); - - let salt_sha = sha2::Sha256::new().chain_update(salt).finalize(); - - let mut hash = [0u8; 32]; - argon - .hash_password_into(secret, &salt_sha, &mut hash) - .unwrap(); - hash - } - }; - SymmetricCryptoKey::try_from(hash.as_slice()) -} - -fn stretch_master_key(master_key: &MasterKey) -> Result { - let key: GenericArray = hkdf_expand(&master_key.0.key, Some("enc"))?; - let mac_key: GenericArray = hkdf_expand(&master_key.0.key, Some("mac"))?; - - Ok(SymmetricCryptoKey { - key, - mac_key: Some(mac_key), - }) -} - -#[cfg(test)] -mod tests { - use std::num::NonZeroU32; - - use super::{stretch_master_key, HashPurpose, MasterKey}; - use crate::{client::kdf::Kdf, crypto::SymmetricCryptoKey}; - - #[test] - fn test_master_key_derive_pbkdf2() { - let master_key = MasterKey::derive( - &b"67t9b5g67$%Dh89n"[..], - "test_key".as_bytes(), - &Kdf::PBKDF2 { - iterations: NonZeroU32::new(10000).unwrap(), - }, - ) - .unwrap(); - - assert_eq!( - [ - 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167, - 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75 - ], - master_key.0.key.as_slice() - ); - assert_eq!(None, master_key.0.mac_key); - } - - #[test] - fn test_master_key_derive_argon2() { - let master_key = MasterKey::derive( - &b"67t9b5g67$%Dh89n"[..], - "test_key".as_bytes(), - &Kdf::Argon2id { - iterations: NonZeroU32::new(4).unwrap(), - memory: NonZeroU32::new(32).unwrap(), - parallelism: NonZeroU32::new(2).unwrap(), - }, - ) - .unwrap(); - - assert_eq!( - [ - 207, 240, 225, 177, 162, 19, 163, 76, 98, 106, 179, 175, 224, 9, 17, 240, 20, 147, - 237, 47, 246, 150, 141, 184, 62, 225, 131, 242, 51, 53, 225, 242 - ], - master_key.0.key.as_slice() - ); - assert_eq!(None, master_key.0.mac_key); - } - - #[test] - fn test_stretch_master_key() { - let master_key = MasterKey(SymmetricCryptoKey { - key: [ - 31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167, - 69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75, - ] - .into(), - mac_key: None, - }); - - let stretched = stretch_master_key(&master_key).unwrap(); - - assert_eq!( - [ - 111, 31, 178, 45, 238, 152, 37, 114, 143, 215, 124, 83, 135, 173, 195, 23, 142, - 134, 120, 249, 61, 132, 163, 182, 113, 197, 189, 204, 188, 21, 237, 96 - ], - stretched.key.as_slice() - ); - assert_eq!( - [ - 221, 127, 206, 234, 101, 27, 202, 38, 86, 52, 34, 28, 78, 28, 185, 16, 48, 61, 127, - 166, 209, 247, 194, 87, 232, 26, 48, 85, 193, 249, 179, 155 - ], - stretched.mac_key.unwrap().as_slice() - ); - } - - #[test] - fn test_password_hash_pbkdf2() { - let password = "asdfasdf".as_bytes(); - let salt = "test_salt".as_bytes(); - let kdf = Kdf::PBKDF2 { - iterations: NonZeroU32::new(100_000).unwrap(), - }; - - let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); - - assert_eq!( - "ZF6HjxUTSyBHsC+HXSOhZoXN+UuMnygV5YkWXCY4VmM=", - master_key - .derive_master_key_hash(password, HashPurpose::ServerAuthorization) - .unwrap(), - ); - } - - #[test] - fn test_password_hash_argon2id() { - let password = "asdfasdf".as_bytes(); - let salt = "test_salt".as_bytes(); - let kdf = Kdf::Argon2id { - iterations: NonZeroU32::new(4).unwrap(), - memory: NonZeroU32::new(32).unwrap(), - parallelism: NonZeroU32::new(2).unwrap(), - }; - - let master_key = MasterKey::derive(password, salt, &kdf).unwrap(); - - assert_eq!( - "PR6UjYmjmppTYcdyTiNbAhPJuQQOmynKbdEl1oyi/iQ=", - master_key - .derive_master_key_hash(password, HashPurpose::ServerAuthorization) - .unwrap(), - ); - } -} diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs deleted file mode 100644 index b35157981..000000000 --- a/crates/bitwarden/src/crypto/mod.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! # Cryptographic primitives -//! -//! This module contains the cryptographic primitives used throughout the SDK. The module makes a -//! best effort to abstract away cryptographic concepts into concepts such as -//! [`EncString`] and [`SymmetricCryptoKey`]. -//! -//! ## Conventions: -//! -//! - Pure Functions that deterministically "derive" keys from input are prefixed with `derive_`. -//! - Functions that generate new keys are prefixed with `make_`. -//! -//! ## Differences from [`clients`](https://github.com/bitwarden/clients) -//! -//! There are some noteworthy differences compared to the other Bitwarden clients. These changes -//! are made in an effort to introduce conventions in how we name things, improve best practices -//! and abstracting away internal complexity. -//! -//! - `CryptoService.makeSendKey` & `AccessService.createAccessToken` are replaced by the generic -//! `derive_shareable_key` -//! - MasterKey operations such as `makeMasterKey` and `hashMasterKey` are moved to the MasterKey -//! struct. -//! - -use aes::cipher::{generic_array::GenericArray, ArrayLength, Unsigned}; -use hmac::digest::OutputSizeUser; - -use crate::error::{Error, Result}; - -mod enc_string; -pub use enc_string::EncString; -mod encryptable; -pub use encryptable::{Decryptable, Encryptable}; -mod aes_ops; -pub use aes_ops::{decrypt_aes256, decrypt_aes256_hmac, encrypt_aes256, encrypt_aes256_hmac}; -mod symmetric_crypto_key; -pub use symmetric_crypto_key::SymmetricCryptoKey; -mod shareable_key; -pub(crate) use shareable_key::derive_shareable_key; - -#[cfg(feature = "internal")] -mod master_key; -#[cfg(feature = "internal")] -pub(crate) use master_key::{HashPurpose, MasterKey}; -#[cfg(feature = "internal")] -mod user_key; -#[cfg(feature = "internal")] -pub(crate) use user_key::UserKey; -#[cfg(feature = "internal")] -mod rsa; -#[cfg(feature = "internal")] -pub use self::rsa::RsaKeyPair; - -#[cfg(feature = "internal")] -mod fingerprint; -#[cfg(feature = "internal")] -pub(crate) use fingerprint::fingerprint; - -pub(crate) type PbkdfSha256Hmac = hmac::Hmac; -pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize = - <::OutputSize as Unsigned>::USIZE; - -/// RFC5869 HKDF-Expand operation -fn hkdf_expand>(prk: &[u8], info: Option<&str>) -> Result> { - let hkdf = hkdf::Hkdf::::from_prk(prk) - .map_err(|_| Error::Internal("invalid prk length"))?; - let mut key = GenericArray::::default(); - - let i = info.map(|i| i.as_bytes()).unwrap_or(&[]); - hkdf.expand(i, &mut key) - .map_err(|_| Error::Internal("invalid length"))?; - - Ok(key) -} diff --git a/crates/bitwarden/src/crypto/rsa.rs b/crates/bitwarden/src/crypto/rsa.rs deleted file mode 100644 index 0d2d135b9..000000000 --- a/crates/bitwarden/src/crypto/rsa.rs +++ /dev/null @@ -1,42 +0,0 @@ -use base64::Engine; -use rsa::{ - pkcs8::{EncodePrivateKey, EncodePublicKey}, - RsaPrivateKey, RsaPublicKey, -}; - -use crate::{ - crypto::{encrypt_aes256_hmac, EncString, SymmetricCryptoKey}, - error::{Error, Result}, - util::BASE64_ENGINE, -}; - -#[cfg_attr(feature = "mobile", derive(uniffi::Record))] -pub struct RsaKeyPair { - /// Base64 encoded DER representation of the public key - pub public: String, - /// Encrypted PKCS8 private key - pub private: EncString, -} - -pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { - let mut rng = rand::thread_rng(); - let bits = 2048; - let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); - let pub_key = RsaPublicKey::from(&priv_key); - - let spki = pub_key - .to_public_key_der() - .map_err(|_| Error::Internal("unable to create public key"))?; - - let b64 = BASE64_ENGINE.encode(spki.as_bytes()); - let pkcs = priv_key - .to_pkcs8_der() - .map_err(|_| Error::Internal("unable to create private key"))?; - - let protected = encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?; - - Ok(RsaKeyPair { - public: b64, - private: protected, - }) -} diff --git a/crates/bitwarden/src/crypto/shareable_key.rs b/crates/bitwarden/src/crypto/shareable_key.rs deleted file mode 100644 index 4127b0712..000000000 --- a/crates/bitwarden/src/crypto/shareable_key.rs +++ /dev/null @@ -1,41 +0,0 @@ -use aes::cipher::{generic_array::GenericArray, typenum::U64}; -use hmac::{Hmac, Mac}; - -use crate::crypto::{hkdf_expand, SymmetricCryptoKey}; - -/// Derive a shareable key using hkdf from secret and name. -/// -/// A specialized variant of this function was called `CryptoService.makeSendKey` in the Bitwarden -/// `clients` repository. -pub(crate) fn derive_shareable_key( - secret: [u8; 16], - name: &str, - info: Option<&str>, -) -> SymmetricCryptoKey { - // Because all inputs are fixed size, we can unwrap all errors here without issue - - // TODO: Are these the final `key` and `info` parameters or should we change them? I followed the pattern used for sends - let res = Hmac::::new_from_slice(format!("bitwarden-{}", name).as_bytes()) - .unwrap() - .chain_update(secret) - .finalize() - .into_bytes(); - - let key: GenericArray = hkdf_expand(&res, info).unwrap(); - - SymmetricCryptoKey::try_from(key.as_slice()).unwrap() -} - -#[cfg(test)] -mod tests { - use super::derive_shareable_key; - - #[test] - fn test_derive_shareable_key() { - let key = derive_shareable_key(*b"&/$%F1a895g67HlX", "test_key", None); - assert_eq!(key.to_base64(), "4PV6+PcmF2w7YHRatvyMcVQtI7zvCyssv/wFWmzjiH6Iv9altjmDkuBD1aagLVaLezbthbSe+ktR+U6qswxNnQ=="); - - let key = derive_shareable_key(*b"67t9b5g67$%Dh89n", "test_key", Some("test")); - assert_eq!(key.to_base64(), "F9jVQmrACGx9VUPjuzfMYDjr726JtL300Y3Yg+VYUnVQtQ1s8oImJ5xtp1KALC9h2nav04++1LDW4iFD+infng=="); - } -} diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden/src/crypto/symmetric_crypto_key.rs deleted file mode 100644 index 7b2086f75..000000000 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::str::FromStr; - -use aes::cipher::{generic_array::GenericArray, typenum::U32}; -use base64::Engine; - -use crate::{ - crypto::derive_shareable_key, - error::{CryptoError, Error}, - util::BASE64_ENGINE, -}; - -/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString) -pub struct SymmetricCryptoKey { - pub key: GenericArray, - pub mac_key: Option>, -} - -impl SymmetricCryptoKey { - const KEY_LEN: usize = 32; - const MAC_LEN: usize = 32; - - pub fn generate(name: &str) -> Self { - use rand::Rng; - let secret: [u8; 16] = rand::thread_rng().gen(); - derive_shareable_key(secret, name, None) - } - - pub fn to_base64(&self) -> String { - let mut buf = Vec::new(); - buf.extend_from_slice(&self.key); - - if let Some(mac) = self.mac_key { - buf.extend_from_slice(&mac); - } - - BASE64_ENGINE.encode(&buf) - } -} - -impl FromStr for SymmetricCryptoKey { - type Err = Error; - - fn from_str(s: &str) -> Result { - let bytes = BASE64_ENGINE - .decode(s) - .map_err(|_| CryptoError::InvalidKey)?; - SymmetricCryptoKey::try_from(bytes.as_slice()) - } -} - -impl TryFrom<&[u8]> for SymmetricCryptoKey { - type Error = Error; - - fn try_from(value: &[u8]) -> Result { - if value.len() == Self::KEY_LEN + Self::MAC_LEN { - Ok(SymmetricCryptoKey { - key: GenericArray::clone_from_slice(&value[..Self::KEY_LEN]), - mac_key: Some(GenericArray::clone_from_slice(&value[Self::KEY_LEN..])), - }) - } else if value.len() == Self::KEY_LEN { - Ok(SymmetricCryptoKey { - key: GenericArray::clone_from_slice(value), - mac_key: None, - }) - } else { - Err(CryptoError::InvalidKeyLen.into()) - } - } -} - -// We manually implement these to make sure we don't print any sensitive data -impl std::fmt::Debug for SymmetricCryptoKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Key").finish() - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::SymmetricCryptoKey; - - #[test] - fn test_symmetric_crypto_key() { - let key = SymmetricCryptoKey::generate("test"); - let key2 = SymmetricCryptoKey::from_str(&key.to_base64()).unwrap(); - assert_eq!(key.key, key2.key); - assert_eq!(key.mac_key, key2.mac_key); - - let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ=="; - let key2 = SymmetricCryptoKey::from_str(key).unwrap(); - assert_eq!(key, key2.to_base64()); - } -} diff --git a/crates/bitwarden/src/crypto/user_key.rs b/crates/bitwarden/src/crypto/user_key.rs deleted file mode 100644 index 0fe560665..000000000 --- a/crates/bitwarden/src/crypto/user_key.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{ - crypto::{ - rsa::{make_key_pair, RsaKeyPair}, - SymmetricCryptoKey, - }, - error::Result, -}; - -pub(crate) struct UserKey(SymmetricCryptoKey); - -impl UserKey { - pub(crate) fn new(key: SymmetricCryptoKey) -> Self { - Self(key) - } - - pub(crate) fn make_key_pair(&self) -> Result { - make_key_pair(&self.0) - } -} diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index 6a9b0d8aa..59fae58d2 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -1,9 +1,13 @@ //! Errors that can occur when using this SDK -use std::fmt::Debug; +use std::{borrow::Cow, fmt::Debug}; use bitwarden_api_api::apis::Error as ApiError; use bitwarden_api_identity::apis::Error as IdentityError; +#[cfg(feature = "internal")] +use bitwarden_exporters::ExportError; +#[cfg(feature = "internal")] +use bitwarden_generators::{PassphraseError, PasswordError, UsernameError}; use reqwest::StatusCode; use thiserror::Error; @@ -20,14 +24,11 @@ pub enum Error { #[error("The response received was invalid and could not be processed")] InvalidResponse, - #[error("The response received was missing some of the required fields")] - MissingFields, + #[error("The response received was missing some of the required fields: {0}")] + MissingFields(&'static str), #[error("Cryptography error, {0}")] - Crypto(#[from] CryptoError), - - #[error("Error parsing EncString: {0}")] - InvalidEncString(#[from] EncStringParseError), + Crypto(#[from] bitwarden_crypto::CryptoError), #[error("Error parsing Identity response: {0}")] IdentityFail(crate::auth::api::response::IdentityTokenFailResponse), @@ -46,8 +47,41 @@ pub enum Error { #[error("Received error message from server: [{}] {}", .status, .message)] ResponseContent { status: StatusCode, message: String }, + #[error("The state file version is invalid")] + InvalidStateFileVersion, + + #[error("The state file could not be read")] + InvalidStateFile, + + // 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), + #[error("Internal error: {0}")] - Internal(&'static str), + Internal(Cow<'static, str>), +} + +impl From for Error { + fn from(s: String) -> Self { + Self::Internal(s.into()) + } +} + +impl From<&'static str> for Error { + fn from(s: &'static str) -> Self { + Self::Internal(s.into()) + } } #[derive(Debug, Error)] @@ -68,34 +102,6 @@ pub enum AccessTokenInvalidError { InvalidBase64Length { expected: usize, got: usize }, } -#[derive(Debug, Error)] -pub enum CryptoError { - #[error("The provided key is not the expected type")] - InvalidKey, - #[error("The cipher's MAC doesn't match the expected value")] - InvalidMac, - #[error("Error while decrypting EncString")] - KeyDecrypt, - #[error("The cipher key has an invalid length")] - InvalidKeyLen, - #[error("There is no encryption key for the provided organization")] - NoKeyForOrg, - #[error("The value is not a valid UTF8 String")] - InvalidUtf8String, -} - -#[derive(Debug, Error)] -pub enum EncStringParseError { - #[error("No type detected, missing '.' separator")] - NoType, - #[error("Invalid type, got {enc_type} with {parts} parts")] - InvalidType { enc_type: String, parts: usize }, - #[error("Error decoding base64: {0}")] - InvalidBase64(#[from] base64::DecodeError), - #[error("Invalid length: expected {expected}, got {got}")] - InvalidLength { expected: usize, got: usize }, -} - // Ensure that the error messages implement Send and Sync #[cfg(test)] const _: () = { @@ -127,4 +133,18 @@ macro_rules! impl_bitwarden_error { 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 a61857992..17774b002 100644 --- a/crates/bitwarden/src/lib.rs +++ b/crates/bitwarden/src/lib.rs @@ -38,22 +38,22 @@ //! let mut client = Client::new(Some(settings)); //! //! // Before we operate, we need to authenticate with a token -//! let token = AccessTokenLoginRequest { access_token: String::from("") }; -//! client.access_token_login(&token).await.unwrap(); +//! let token = AccessTokenLoginRequest { access_token: String::from(""), state_file: None }; +//! client.auth().login_access_token(&token).await.unwrap(); //! //! let org_id = SecretIdentifiersRequest { organization_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() }; //! println!("Stored secrets: {:#?}", client.secrets().list(&org_id).await.unwrap()); //! Ok(()) //! } //! ``` -//! #[cfg(feature = "mobile")] uniffi::setup_scaffolding!(); +#[cfg(feature = "internal")] +pub mod admin_console; pub mod auth; pub mod client; -pub mod crypto; pub mod error; #[cfg(feature = "mobile")] pub mod mobile; @@ -66,12 +66,18 @@ pub mod tool; #[cfg(feature = "mobile")] pub(crate) mod uniffi_support; mod util; -#[cfg(feature = "mobile")] +#[cfg(feature = "internal")] pub mod vault; -pub mod wordlist; pub use client::Client; // Ensure the readme docs compile #[doc = include_str!("../README.md")] mod readme {} + +#[cfg(feature = "internal")] +pub mod generators { + pub use bitwarden_generators::{ + PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest, + }; +} diff --git a/crates/bitwarden/src/mobile/client_crypto.rs b/crates/bitwarden/src/mobile/client_crypto.rs index 67f69d8f1..26451ffaa 100644 --- a/crates/bitwarden/src/mobile/client_crypto.rs +++ b/crates/bitwarden/src/mobile/client_crypto.rs @@ -1,8 +1,15 @@ +#[cfg(feature = "internal")] +use bitwarden_crypto::{AsymmetricEncString, EncString, SensitiveString}; + use crate::Client; #[cfg(feature = "internal")] use crate::{ error::Result, - mobile::crypto::{initialize_crypto, InitCryptoRequest}, + 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> { @@ -11,8 +18,44 @@ pub struct ClientCrypto<'a> { impl<'a> ClientCrypto<'a> { #[cfg(feature = "internal")] - pub async fn initialize_crypto(&mut self, req: InitCryptoRequest) -> Result<()> { - initialize_crypto(self.client, req).await + pub async fn initialize_user_crypto(&mut self, req: InitUserCryptoRequest) -> Result<()> { + initialize_user_crypto(self.client, req).await + } + + #[cfg(feature = "internal")] + pub async fn initialize_org_crypto(&mut self, req: InitOrgCryptoRequest) -> Result<()> { + initialize_org_crypto(self.client, req).await + } + + #[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) } } diff --git a/crates/bitwarden/src/mobile/client_kdf.rs b/crates/bitwarden/src/mobile/client_kdf.rs index 320a03571..4e62e5d59 100644 --- a/crates/bitwarden/src/mobile/client_kdf.rs +++ b/crates/bitwarden/src/mobile/client_kdf.rs @@ -1,4 +1,6 @@ -use crate::{client::kdf::Kdf, error::Result, mobile::kdf::hash_password, Client}; +use bitwarden_crypto::HashPurpose; + +use crate::{client::Kdf, error::Result, mobile::kdf::hash_password, Client}; pub struct ClientKdf<'a> { pub(crate) client: &'a crate::Client, @@ -10,8 +12,9 @@ impl<'a> ClientKdf<'a> { email: String, password: String, kdf_params: Kdf, + purpose: HashPurpose, ) -> Result { - hash_password(self.client, email, password, kdf_params).await + hash_password(self.client, email, password, kdf_params, purpose).await } } diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index f4d26dc6c..b8891aa09 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -1,31 +1,97 @@ use std::collections::HashMap; +use bitwarden_crypto::{AsymmetricEncString, EncString}; +#[cfg(feature = "internal")] +use bitwarden_crypto::{ + KeyDecryptable, KeyEncryptable, MasterKey, SensitiveString, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{client::kdf::Kdf, crypto::EncString, error::Result, Client}; +#[cfg(feature = "internal")] +use crate::client::{LoginMethod, UserLoginMethod}; +use crate::{ + client::Kdf, + error::{Error, Result}, + Client, +}; #[cfg(feature = "internal")] #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] -pub struct InitCryptoRequest { +pub struct InitUserCryptoRequest { /// The user's KDF parameters, as received from the prelogin request pub kdf_params: Kdf, /// The user's email address pub email: String, - /// The user's master password - pub password: String, - /// The user's encrypted symmetric crypto key - pub user_key: String, - /// The user's encryptred private key + /// The user's encrypted private key pub private_key: String, - /// The encryption keys for all the organizations the user is a part of - pub organization_keys: HashMap, + /// The initialization method to use + pub method: InitUserCryptoMethod, +} + +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum InitUserCryptoMethod { + Password { + /// The user's master password + password: String, + /// The user's encrypted symmetric crypto key + user_key: String, + }, + DecryptedKey { + /// The user's decrypted encryption key, obtained using `get_user_encryption_key` + decrypted_user_key: String, + }, + Pin { + /// The user's PIN + pin: String, + /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain + /// this. + pin_protected_user_key: EncString, + }, + AuthRequest { + /// Private Key generated by the `crate::auth::new_auth_request`. + request_private_key: String, + + method: AuthRequestMethod, + }, + DeviceKey { + /// The device's DeviceKey + device_key: String, + /// The Device Private Key + protected_device_private_key: EncString, + /// The user's symmetric crypto key, encrypted with the Device Key. + device_protected_user_key: AsymmetricEncString, + }, +} + +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum AuthRequestMethod { + UserKey { + /// User Key protected by the private key provided in `AuthRequestResponse`. + protected_user_key: AsymmetricEncString, + }, + MasterKey { + /// Master Key protected by the private key provided in `AuthRequestResponse`. + protected_master_key: AsymmetricEncString, + /// User Key protected by the MasterKey, provided by the auth response. + auth_request_key: EncString, + }, } #[cfg(feature = "internal")] -pub async fn initialize_crypto(client: &mut Client, req: InitCryptoRequest) -> Result<()> { +pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { + use bitwarden_crypto::{DecryptedString, DeviceKey}; + + use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key}; + let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), email: req.email, @@ -33,18 +99,428 @@ pub async fn initialize_crypto(client: &mut Client, req: InitCryptoRequest) -> R }); client.set_login_method(login_method); - let user_key = req.user_key.parse::()?; - let private_key = req.private_key.parse::()?; + let private_key: EncString = req.private_key.parse()?; - client.initialize_user_crypto(&req.password, user_key, private_key)?; + match req.method { + InitUserCryptoMethod::Password { password, user_key } => { + let user_key: EncString = user_key.parse()?; + client.initialize_user_crypto(&password, user_key, private_key)?; + } + InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => { + let decrypted_user_key = DecryptedString::new(Box::new(decrypted_user_key)); + let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?; + client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + } + InitUserCryptoMethod::Pin { + pin, + pin_protected_user_key, + } => { + client.initialize_user_crypto_pin(&pin, pin_protected_user_key, private_key)?; + } + InitUserCryptoMethod::AuthRequest { + request_private_key, + method, + } => { + let user_key = match method { + AuthRequestMethod::UserKey { protected_user_key } => { + auth_request_decrypt_user_key(request_private_key, protected_user_key)? + } + AuthRequestMethod::MasterKey { + protected_master_key, + auth_request_key, + } => auth_request_decrypt_master_key( + request_private_key, + protected_master_key, + auth_request_key, + )?, + }; + client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + } + InitUserCryptoMethod::DeviceKey { + device_key, + protected_device_private_key, + device_protected_user_key, + } => { + let device_key = DecryptedString::new(Box::new(device_key)); + let device_key = DeviceKey::try_from(device_key)?; + let user_key = device_key + .decrypt_user_key(protected_device_private_key, device_protected_user_key)?; - let organization_keys = req - .organization_keys - .into_iter() - .map(|(k, v)| Ok((k, v.parse::()?))) - .collect::>>()?; + client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + } + } - client.initialize_org_crypto(organization_keys)?; + Ok(()) +} + +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct InitOrgCryptoRequest { + /// The encryption keys for all the organizations the user is a part of + pub organization_keys: HashMap, +} +#[cfg(feature = "internal")] +pub async fn initialize_org_crypto(client: &mut Client, req: InitOrgCryptoRequest) -> Result<()> { + let organization_keys = req.organization_keys.into_iter().collect(); + client.initialize_org_crypto(organization_keys)?; Ok(()) } + +#[cfg(feature = "internal")] +pub async fn get_user_encryption_key(client: &mut Client) -> Result { + let user_key = client + .get_encryption_settings()? + .get_key(&None) + .ok_or(Error::VaultLocked)?; + + Ok(user_key.to_base64()) +} + +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct UpdatePasswordResponse { + /// Hash of the new password + password_hash: String, + /// User key, encrypted with the new password + 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)?; + + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + // Derive a new master key from password + let new_master_key = match login_method { + LoginMethod::User( + UserLoginMethod::Username { email, kdf, .. } + | UserLoginMethod::ApiKey { email, kdf, .. }, + ) => MasterKey::derive(new_password.as_bytes(), email.as_bytes(), kdf)?, + _ => return Err(Error::NotAuthenticated), + }; + + let new_key = new_master_key.encrypt_user_key(user_key)?; + + let password_hash = new_master_key.derive_master_key_hash( + new_password.as_bytes(), + bitwarden_crypto::HashPurpose::ServerAuthorization, + )?; + + Ok(UpdatePasswordResponse { + password_hash, + new_key, + }) +} + +#[cfg(feature = "internal")] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct DerivePinKeyResponse { + /// [UserKey] protected by PIN + pin_protected_user_key: EncString, + /// PIN protected by [UserKey] + 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)?; + + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + let pin_protected_user_key = derive_pin_protected_user_key(&pin, login_method, user_key)?; + + Ok(DerivePinKeyResponse { + pin_protected_user_key, + encrypted_pin: pin.encrypt_with_key(user_key)?, + }) +} + +#[cfg(feature = "internal")] +pub fn derive_pin_user_key(client: &mut Client, encrypted_pin: EncString) -> Result { + let user_key = client + .get_encryption_settings()? + .get_key(&None) + .ok_or(Error::VaultLocked)?; + + let pin: String = encrypted_pin.decrypt_with_key(user_key)?; + let login_method = client + .login_method + .as_ref() + .ok_or(Error::NotAuthenticated)?; + + 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 { + 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), + }; + + Ok(derived_key.encrypt_user_key(user_key)?) +} + +#[cfg(feature = "internal")] +pub(super) fn enroll_admin_password_reset( + client: &mut 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)?; + + Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( + key.to_vec().expose(), + &public_key, + )?) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{client::Kdf, Client}; + + #[tokio::test] + async fn test_update_password() { + let mut client = Client::new(None); + + let priv_key = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw="; + + let kdf = Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }; + + initialize_user_crypto( + &mut client, + InitUserCryptoRequest { + kdf_params: kdf.clone(), + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Password { + password: "asdfasdfasdf".into(), + user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), + }, + }, + ) + .await + .unwrap(); + + let new_password_response = update_password(&mut client, "123412341234".into()).unwrap(); + + let mut client2 = Client::new(None); + + initialize_user_crypto( + &mut client2, + InitUserCryptoRequest { + kdf_params: kdf.clone(), + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Password { + password: "123412341234".into(), + user_key: new_password_response.new_key.to_string(), + }, + }, + ) + .await + .unwrap(); + + let new_hash = client2 + .kdf() + .hash_password( + "test@bitwarden.com".into(), + "123412341234".into(), + kdf.clone(), + bitwarden_crypto::HashPurpose::ServerAuthorization, + ) + .await + .unwrap(); + + assert_eq!(new_hash, new_password_response.password_hash); + + assert_eq!( + client + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64(), + client2 + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64() + ); + } + + #[tokio::test] + async fn test_initialize_user_crypto_pin() { + let mut client = Client::new(None); + + let priv_key = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw="; + + initialize_user_crypto( + &mut client, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Password { + password: "asdfasdfasdf".into(), + user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".into(), + }, + }, + ) + .await + .unwrap(); + + let pin_key = derive_pin_key(&mut client, "1234".into()).unwrap(); + + // Verify we can unlock with the pin + let mut client2 = Client::new(None); + initialize_user_crypto( + &mut client2, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Pin { + pin: "1234".into(), + pin_protected_user_key: pin_key.pin_protected_user_key, + }, + }, + ) + .await + .unwrap(); + + assert_eq!( + client + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64(), + client2 + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64() + ); + + // 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 mut client3 = Client::new(None); + + initialize_user_crypto( + &mut client3, + InitUserCryptoRequest { + kdf_params: Kdf::PBKDF2 { + iterations: 100_000.try_into().unwrap(), + }, + email: "test@bitwarden.com".into(), + private_key: priv_key.to_owned(), + method: InitUserCryptoMethod::Pin { + pin: "1234".into(), + pin_protected_user_key, + }, + }, + ) + .await + .unwrap(); + + assert_eq!( + client + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64(), + client3 + .get_encryption_settings() + .unwrap() + .get_key(&None) + .unwrap() + .to_base64() + ); + } + + #[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); + 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(), + }, + })); + + 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 + .initialize_user_crypto("asdfasdfasdf", 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 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(); + assert_eq!(&decrypted, expected.to_vec().expose()); + } +} diff --git a/crates/bitwarden/src/mobile/kdf.rs b/crates/bitwarden/src/mobile/kdf.rs index a34b339bb..1c1972086 100644 --- a/crates/bitwarden/src/mobile/kdf.rs +++ b/crates/bitwarden/src/mobile/kdf.rs @@ -1,17 +1,15 @@ -use crate::{ - client::kdf::Kdf, - crypto::{HashPurpose, MasterKey}, - error::Result, - Client, -}; +use bitwarden_crypto::{HashPurpose, Kdf, MasterKey}; + +use crate::{error::Result, Client}; 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)?; - master_key.derive_master_key_hash(password.as_bytes(), HashPurpose::ServerAuthorization) + Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) } diff --git a/crates/bitwarden/src/mobile/mod.rs b/crates/bitwarden/src/mobile/mod.rs index ad2d794af..e0ba50d00 100644 --- a/crates/bitwarden/src/mobile/mod.rs +++ b/crates/bitwarden/src/mobile/mod.rs @@ -3,17 +3,8 @@ pub mod crypto; pub mod kdf; pub mod vault; -pub(crate) mod client_crypto; -pub(crate) mod client_kdf; +mod client_crypto; +mod client_kdf; -// Usually we wouldn't want to expose EncStrings in the API or the schemas, -// but we need them in the mobile API, so define it here to limit the scope -impl schemars::JsonSchema for crate::crypto::EncString { - fn schema_name() -> String { - "EncString".to_string() - } - - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - gen.subschema_for::() - } -} +pub use client_crypto::ClientCrypto; +pub use client_kdf::ClientKdf; diff --git a/crates/bitwarden/src/mobile/vault/client_attachments.rs b/crates/bitwarden/src/mobile/vault/client_attachments.rs new file mode 100644 index 000000000..e40721b04 --- /dev/null +++ b/crates/bitwarden/src/mobile/vault/client_attachments.rs @@ -0,0 +1,89 @@ +use std::path::Path; + +use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey}; + +use super::client_vault::ClientVault; +use crate::{ + error::{Error, Result}, + vault::{ + Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView, + Cipher, + }, + Client, +}; + +pub struct ClientAttachments<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientAttachments<'a> { + pub async 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)?; + + Ok(AttachmentFileView { + cipher, + attachment, + contents: buffer, + } + .encrypt_with_key(key)?) + } + pub async fn encrypt_file( + &self, + cipher: Cipher, + attachment: AttachmentView, + decrypted_file_path: &Path, + encrypted_file_path: &Path, + ) -> Result { + let data = std::fs::read(decrypted_file_path)?; + let AttachmentEncryptResult { + attachment, + contents, + } = self.encrypt_buffer(cipher, attachment, &data).await?; + std::fs::write(encrypted_file_path, contents)?; + Ok(attachment) + } + + pub async 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)?; + + AttachmentFile { + cipher, + attachment, + contents: EncString::from_buffer(encrypted_buffer)?, + } + .decrypt_with_key(key) + .map_err(Error::Crypto) + } + pub async fn decrypt_file( + &self, + cipher: Cipher, + attachment: Attachment, + encrypted_file_path: &Path, + decrypted_file_path: &Path, + ) -> Result<()> { + let data = std::fs::read(encrypted_file_path)?; + let decrypted = self.decrypt_buffer(cipher, attachment, &data).await?; + std::fs::write(decrypted_file_path, decrypted)?; + Ok(()) + } +} + +impl<'a> ClientVault<'a> { + pub fn attachments(&'a self) -> ClientAttachments<'a> { + ClientAttachments { + client: self.client, + } + } +} diff --git a/crates/bitwarden/src/mobile/vault/client_ciphers.rs b/crates/bitwarden/src/mobile/vault/client_ciphers.rs index 87b77cfd5..71203374f 100644 --- a/crates/bitwarden/src/mobile/vault/client_ciphers.rs +++ b/crates/bitwarden/src/mobile/vault/client_ciphers.rs @@ -1,7 +1,9 @@ +use bitwarden_crypto::{Decryptable, Encryptable, LocateKey}; +use uuid::Uuid; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, Encryptable}, - error::Result, + error::{Error, Result}, vault::{Cipher, CipherListView, CipherView}, Client, }; @@ -11,9 +13,18 @@ pub struct ClientCiphers<'a> { } impl<'a> ClientCiphers<'a> { - pub async fn encrypt(&self, cipher_view: CipherView) -> Result { + pub async fn encrypt(&self, mut cipher_view: CipherView) -> Result { let enc = self.client.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)?; + cipher_view.generate_cipher_key(key)?; + } + let cipher = cipher_view.encrypt(enc, &None)?; Ok(cipher) @@ -34,6 +45,16 @@ impl<'a> ClientCiphers<'a> { Ok(cipher_views) } + + pub async 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)?; + Ok(cipher_view) + } } impl<'a> ClientVault<'a> { diff --git a/crates/bitwarden/src/mobile/vault/client_collection.rs b/crates/bitwarden/src/mobile/vault/client_collection.rs index 80535406c..9cb5d1711 100644 --- a/crates/bitwarden/src/mobile/vault/client_collection.rs +++ b/crates/bitwarden/src/mobile/vault/client_collection.rs @@ -1,6 +1,7 @@ +use bitwarden_crypto::Decryptable; + use super::client_vault::ClientVault; use crate::{ - crypto::Decryptable, error::Result, vault::{Collection, CollectionView}, Client, diff --git a/crates/bitwarden/src/mobile/vault/client_folders.rs b/crates/bitwarden/src/mobile/vault/client_folders.rs index b6f6a72f4..fec3ad7db 100644 --- a/crates/bitwarden/src/mobile/vault/client_folders.rs +++ b/crates/bitwarden/src/mobile/vault/client_folders.rs @@ -1,6 +1,7 @@ +use bitwarden_crypto::{Decryptable, Encryptable}; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, Encryptable}, error::Result, vault::{Folder, FolderView}, Client, diff --git a/crates/bitwarden/src/mobile/vault/client_password_history.rs b/crates/bitwarden/src/mobile/vault/client_password_history.rs index d873f8dfc..99727232b 100644 --- a/crates/bitwarden/src/mobile/vault/client_password_history.rs +++ b/crates/bitwarden/src/mobile/vault/client_password_history.rs @@ -1,6 +1,7 @@ +use bitwarden_crypto::{Decryptable, Encryptable}; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, Encryptable}, error::Result, vault::{PasswordHistory, PasswordHistoryView}, Client, diff --git a/crates/bitwarden/src/mobile/vault/client_sends.rs b/crates/bitwarden/src/mobile/vault/client_sends.rs index 0adef3410..e03432313 100644 --- a/crates/bitwarden/src/mobile/vault/client_sends.rs +++ b/crates/bitwarden/src/mobile/vault/client_sends.rs @@ -1,9 +1,10 @@ use std::path::Path; +use bitwarden_crypto::{Decryptable, EncString, Encryptable, KeyDecryptable, KeyEncryptable}; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, EncString, Encryptable}, - error::Result, + error::{Error, Result}, vault::{Send, SendListView, SendView}, Client, }; @@ -35,7 +36,7 @@ impl<'a> ClientSends<'a> { encrypted_file_path: &Path, decrypted_file_path: &Path, ) -> Result<()> { - let data = std::fs::read(encrypted_file_path).unwrap(); + let data = std::fs::read(encrypted_file_path)?; let decrypted = self.decrypt_buffer(send, &data).await?; std::fs::write(decrypted_file_path, decrypted)?; Ok(()) @@ -43,11 +44,11 @@ impl<'a> ClientSends<'a> { pub async fn decrypt_buffer(&self, send: Send, encrypted_buffer: &[u8]) -> Result> { let enc = self.client.get_encryption_settings()?; - let enc = Send::get_encryption(&send.key, enc, &None)?; + 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)?; - - enc.decrypt_bytes(&buf, &None) + Ok(buf.decrypt_with_key(&key)?) } pub async fn encrypt(&self, send_view: SendView) -> Result { @@ -64,18 +65,22 @@ impl<'a> ClientSends<'a> { decrypted_file_path: &Path, encrypted_file_path: &Path, ) -> Result<()> { - let data = std::fs::read(decrypted_file_path).unwrap(); + 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 enc = self.client.get_encryption_settings()?; - let enc = Send::get_encryption(&send.key, enc, &None)?; - - let enc = enc.encrypt(buffer, &None)?; - enc.to_buffer() + 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()?) } } diff --git a/crates/bitwarden/src/mobile/vault/client_totp.rs b/crates/bitwarden/src/mobile/vault/client_totp.rs new file mode 100644 index 000000000..3d3b80f98 --- /dev/null +++ b/crates/bitwarden/src/mobile/vault/client_totp.rs @@ -0,0 +1,23 @@ +use chrono::{DateTime, Utc}; + +use super::client_vault::ClientVault; +use crate::{ + error::Result, + vault::{generate_totp, 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 index f0fe4ca76..a2d4d91b3 100644 --- a/crates/bitwarden/src/mobile/vault/mod.rs +++ b/crates/bitwarden/src/mobile/vault/mod.rs @@ -1,6 +1,16 @@ +mod client_attachments; mod client_ciphers; mod client_collection; mod client_folders; mod client_password_history; mod client_sends; +mod client_totp; mod client_vault; + +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; +pub use client_sends::ClientSends; +pub use client_vault::ClientVault; diff --git a/crates/bitwarden/src/platform/client_platform.rs b/crates/bitwarden/src/platform/client_platform.rs new file mode 100644 index 000000000..ada8b92b8 --- /dev/null +++ b/crates/bitwarden/src/platform/client_platform.rs @@ -0,0 +1,25 @@ +use super::{ + generate_fingerprint::{generate_fingerprint, generate_user_fingerprint}, + FingerprintRequest, FingerprintResponse, +}; +use crate::{error::Result, Client}; + +pub struct ClientPlatform<'a> { + pub(crate) client: &'a mut Client, +} + +impl<'a> ClientPlatform<'a> { + pub fn fingerprint(&self, input: &FingerprintRequest) -> Result { + generate_fingerprint(input) + } + + pub fn user_fingerprint(self, fingerprint_material: String) -> Result { + generate_user_fingerprint(self.client, fingerprint_material) + } +} + +impl<'a> Client { + pub fn platform(&'a mut self) -> ClientPlatform<'a> { + ClientPlatform { client: self } + } +} diff --git a/crates/bitwarden/src/platform/domain.rs b/crates/bitwarden/src/platform/domain.rs new file mode 100644 index 000000000..482cb1f59 --- /dev/null +++ b/crates/bitwarden/src/platform/domain.rs @@ -0,0 +1,23 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::error::{require, Error, Result}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +pub struct GlobalDomains { + pub r#type: i32, + pub domains: Vec, + pub excluded: bool, +} + +impl TryFrom for GlobalDomains { + type Error = Error; + + fn try_from(global_domains: bitwarden_api_api::models::GlobalDomains) -> Result { + Ok(Self { + r#type: require!(global_domains.r#type), + domains: require!(global_domains.domains), + excluded: require!(global_domains.excluded), + }) + } +} diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index 800d6e847..59d81d652 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -1,12 +1,14 @@ -use base64::Engine; -use log::{debug, info}; +use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::fingerprint; +use log::info; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{crypto::fingerprint, error::Result, util::BASE64_ENGINE}; +use crate::error::Result; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct FingerprintRequest { /// The input material, used in the fingerprint generation process. pub fingerprint_material: String, @@ -22,11 +24,67 @@ pub struct FingerprintResponse { pub(crate) fn generate_fingerprint(input: &FingerprintRequest) -> Result { info!("Generating fingerprint"); - debug!("{:?}", input); - let key = BASE64_ENGINE.decode(&input.public_key)?; + let key = STANDARD.decode(&input.public_key)?; Ok(FingerprintResponse { fingerprint: fingerprint(&input.fingerprint_material, &key)?, }) } + +pub(crate) fn generate_user_fingerprint( + client: &mut crate::Client, + fingerprint_material: String, +) -> Result { + info!("Generating fingerprint"); + + let enc_settings = client.get_encryption_settings()?; + let private_key = enc_settings + .private_key + .as_ref() + .ok_or("Missing private key")?; + + let public_key = private_key.to_public_der()?; + let fingerprint = fingerprint(&fingerprint_material, &public_key)?; + + Ok(fingerprint) +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use super::*; + use crate::{ + client::{Kdf, LoginMethod, UserLoginMethod}, + Client, + }; + + #[test] + fn test_generate_user_fingerprint() { + let user_key = "2.oZg5RYpU2HjUAKI1DUQCkg==|PyRzI9kZpt66P2OedH8CHOeU0/lgKLkhIJiKDijdyFqIemBSIBoslhfQh/P1TK9xgZp0smgD6+5+yNbZfOpBaCVrsT3WWAO78xOWizduRe4=|xfDLDZSJ+yZAdh388flVg7SMDBJuMs0+CHTjutKs4uQ="; + let private_key = "2.tY6WsWKUbBwNU8wROuipiQ==|DNFL1d19xVojUKTTy2gxT+9J1VXbMQLcbMnx1HSeA6U3yZhsLR6DPaGibb3Bp8doIHtrsxzL/JeLb4gLDZ8RnDhFfE4iLRaPakX14kbBXrKH9/uW/zc7TqIVciWhI1PaeFlu8wnVuGt3e5Ysx6Y7Uw7RS8pRT5aE3sX3aDPGZTAdTutLn1VUfkShS5OK5HJl9CdiwV2wOcrf4w/WqtaNUUqGdsJ8C4ELlpBzHxqs+lEm+8pGPYmuGQIjVc0eOR9Tza9GTk3ih1XGc1znOCoKUZbtA29RfbwfmJy/yGi/3RLWZFQGCCij4cLC5OpldiX4JWL5Dhox44p/5IVF3rfxTVz3GCyDOoHevRG/06sUBq6nhbdCQf3lJvxwcQJhoQg4rsapM3rgol+u+TbXRiwWPbfswuLkRlvGFKtKUWMa4S57gj0CFYgSBPdTyhZTB44D7JQ2bd901Ur1dYWcDe4Kn3ZawpxL0cX2ZPlE3v8FXFJf2s8DJytL8yu73GasDzVmaGHxueWWVz7EHjh+pmB4oaAHARcY8d3LActAyl/+bcFRPYQJ68ae6DJhYYJGHIBWMImf2BifGgUX8vUFfUAYjne3D82lRyZQHs3xbl+ZxEPgWiPYRWUtxGXLLP4f9mbl+LeJdehtHNjC8kOduBL0CsP4gmugzNNUXI+Izc/9svno6kFr6SU0LA3MGrOU8ao7UCQbf/Pj/RKnG1gRmBDQqf7IMm6jOyTwdde9NpfQb32iH11PkuAKBvEtUuq9BeAKWjoZku+ycsN2jZH0hzd/QrU2c+E4+yHwX3wSxxorNOXt5EZkJbEDBlpRyE1zWoyy0wIYfcChYLvFN8QFHchlw5wmHxL+OOgdgndAtV/2DCx+NB6caY31qLictME+1GPPlQ7QvicMLgmpSWq83rs4ex/My6p3hCRSrJJiLvjEDZLYWKHHLd5tsPRAjX8ADNWB1VeIeiJrj1wpOCc1PbWpbljbbTsBmVPo6iKm/UDGAHBdQ//0j3FQg8f5w/j+McsoaMpDNHNTiLvjWERR+RBmsEA0lEL00wZz/DHlzOAYHLYYqFMT7GBCQD+Wk/l1TL+X2agUy7Irlk7QbZ4ivfdNIpSW8Ct9MGE6o4wV+nIpXURojgBBTcP85RTBLXXGrIprnK1G/VE8ONag3+nkqIyChjYyk5QMsxqOqSHsbiOxhCdXypbCbY4g9yKJtBJ/ADjxmELj0X7pqsTFqC0eRT7rk9qTBcYBBu6rwlAfq8AKjDB7WjNjzLaMi6lBoe4petBn1xcLkXD5hHra0TULxcYrq8MIb+Vk4CBZZdwwyVm/28SwSjHBIBpRysPAonDDsp3KlahwXEFvRDQR/oFww172GI7cx8SoPn93Qh0JfpTAAowsO3meR8bzUSyd7v3rmtaBPsWHE9zUXye/6nloMU5joEcD6uJaxd0kdaWWIoKLH++zHW1R776wJrS6u+TIWZgHqiIJoCd9fV25BnQcbZRKd6mnfNQkchJ6c6ozXKrFaa8DLdERdfh84+isw5mzW2zMJwHEwtKt6LUTyieC2exzPAwPxJT1+IMjuzuwiLnvGKOq+kwE/LWBSB0ZfGuCP/3jMM8OCfe7Hbpt1TfXcUxUzj6sSjkjQB6qBt+TINRdOFA=|fppguME86utsAOKrBYn6XU95q7daVbZ+3dD9OVkQlAw="; + let fingerprint_material = "a09726a0-9590-49d1-a5f5-afe300b6a515"; + + let mut client = Client::new(None); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "a09726a0-9590-49d1-a5f5-afe300b6a515".to_owned(), + email: "robb@stark.com".to_owned(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }, + })); + client + .initialize_user_crypto( + "asdfasdfasdf", + user_key.parse().unwrap(), + private_key.parse().unwrap(), + ) + .unwrap(); + + let fingerprint = + generate_user_fingerprint(&mut client, fingerprint_material.to_string()).unwrap(); + + assert_eq!(fingerprint, "turban-deftly-anime-chatroom-unselfish"); + } +} diff --git a/crates/bitwarden/src/platform/get_user_api_key.rs b/crates/bitwarden/src/platform/get_user_api_key.rs index a1cfc389d..3e408d926 100644 --- a/crates/bitwarden/src/platform/get_user_api_key.rs +++ b/crates/bitwarden/src/platform/get_user_api_key.rs @@ -2,6 +2,7 @@ use bitwarden_api_api::{ apis::accounts_api::accounts_api_key_post, models::{ApiKeyResponseModel, SecretVerificationRequestModel}, }; +use bitwarden_crypto::{HashPurpose, MasterKey}; use log::{debug, info}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -9,8 +10,7 @@ use serde::{Deserialize, Serialize}; use super::SecretVerificationRequest; use crate::{ client::{LoginMethod, UserLoginMethod}, - crypto::{HashPurpose, MasterKey}, - error::{Error, Result}, + error::{require, Error, Result}, Client, }; @@ -62,7 +62,7 @@ fn get_secret_verification_request( auth_request_access_code: None, }) } else { - Err(Error::Internal("Unsupported login method")) + Err("Unsupported login method".into()) } } @@ -75,9 +75,7 @@ pub struct UserApiKeyResponse { impl UserApiKeyResponse { pub(crate) fn process_response(response: ApiKeyResponseModel) -> Result { - match response.api_key { - Some(api_key) => Ok(UserApiKeyResponse { api_key }), - None => Err(Error::MissingFields), - } + let api_key = require!(response.api_key); + Ok(UserApiKeyResponse { api_key }) } } diff --git a/crates/bitwarden/src/platform/mod.rs b/crates/bitwarden/src/platform/mod.rs index 0224e8d3a..b78cb27d4 100644 --- a/crates/bitwarden/src/platform/mod.rs +++ b/crates/bitwarden/src/platform/mod.rs @@ -1,9 +1,10 @@ +pub mod client_platform; +mod domain; mod generate_fingerprint; mod get_user_api_key; mod secret_verification_request; mod sync; -pub(crate) use generate_fingerprint::generate_fingerprint; pub use generate_fingerprint::{FingerprintRequest, FingerprintResponse}; pub(crate) use get_user_api_key::get_user_api_key; pub use get_user_api_key::UserApiKeyResponse; diff --git a/crates/bitwarden/src/platform/secret_verification_request.rs b/crates/bitwarden/src/platform/secret_verification_request.rs index b501d0181..e05926620 100644 --- a/crates/bitwarden/src/platform/secret_verification_request.rs +++ b/crates/bitwarden/src/platform/secret_verification_request.rs @@ -4,10 +4,11 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SecretVerificationRequest { - /// The user's master password to use for user verification. If supplied, this will be used for verification - /// purposes. + /// The user's master password to use for user verification. If supplied, this will be used for + /// verification purposes. pub master_password: Option, - /// 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. + /// 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. pub otp: Option, } diff --git a/crates/bitwarden/src/platform/sync.rs b/crates/bitwarden/src/platform/sync.rs index fd33e4343..5a5c6ee82 100644 --- a/crates/bitwarden/src/platform/sync.rs +++ b/crates/bitwarden/src/platform/sync.rs @@ -1,14 +1,16 @@ use bitwarden_api_api::models::{ - CipherDetailsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel, - SyncResponseModel, + DomainsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel, SyncResponseModel, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use super::domain::GlobalDomains; use crate::{ + admin_console::Policy, client::{encryption_settings::EncryptionSettings, Client}, - error::{Error, Result}, + error::{require, Error, Result}, + vault::{Cipher, Collection, Folder}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -23,10 +25,7 @@ pub(crate) async fn sync(client: &mut Client, input: &SyncRequest) -> Result = sync - .profile - .as_ref() - .ok_or(Error::MissingFields)? + let org_keys: Vec<_> = require!(sync.profile.as_ref()) .organizations .as_deref() .unwrap_or_default() @@ -59,15 +58,24 @@ pub struct ProfileOrganizationResponse { #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct CipherDetailsResponse {} +pub struct DomainResponse { + pub equivalent_domains: Vec>, + pub global_equivalent_domains: Vec, +} #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SyncResponse { - /// Data about the user, including their encryption keys and the organizations they are a part of + /// Data about the user, including their encryption keys and the organizations they are a part + /// of pub profile: ProfileResponse, - /// List of ciphers accesible by the user - pub ciphers: Vec, + pub folders: Vec, + pub collections: Vec, + /// List of ciphers accessible by the user + pub ciphers: Vec, + pub domains: Option, + pub policies: Vec, + pub sends: Vec, } impl SyncResponse { @@ -75,31 +83,36 @@ impl SyncResponse { response: SyncResponseModel, enc: &EncryptionSettings, ) -> Result { - let profile = *response.profile.ok_or(Error::MissingFields)?; - let ciphers = response.ciphers.ok_or(Error::MissingFields)?; + let profile = require!(response.profile); + let ciphers = require!(response.ciphers); + + fn try_into_iter(iter: In) -> Result + where + In: IntoIterator, + InItem: TryInto, + Out: FromIterator, + { + iter.into_iter().map(|i| i.try_into()).collect() + } Ok(SyncResponse { - profile: ProfileResponse::process_response(profile, enc)?, - ciphers: ciphers - .into_iter() - .map(CipherDetailsResponse::process_response) - .collect::>()?, + profile: ProfileResponse::process_response(*profile, enc)?, + folders: try_into_iter(require!(response.folders))?, + 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))?, }) } } -impl CipherDetailsResponse { - fn process_response(_response: CipherDetailsResponseModel) -> Result { - Ok(CipherDetailsResponse {}) - } -} - impl ProfileOrganizationResponse { fn process_response( response: ProfileOrganizationResponseModel, ) -> Result { Ok(ProfileOrganizationResponse { - id: response.id.ok_or(Error::MissingFields)?, + id: require!(response.id), }) } } @@ -110,9 +123,9 @@ impl ProfileResponse { _enc: &EncryptionSettings, ) -> Result { Ok(ProfileResponse { - id: response.id.ok_or(Error::MissingFields)?, - name: response.name.ok_or(Error::MissingFields)?, - email: response.email.ok_or(Error::MissingFields)?, + id: require!(response.id), + name: require!(response.name), + email: require!(response.email), //key: response.key, //private_key: response.private_key, organizations: response @@ -124,3 +137,18 @@ impl ProfileResponse { }) } } + +impl TryFrom for DomainResponse { + type Error = Error; + fn try_from(value: DomainsResponseModel) -> Result { + Ok(Self { + equivalent_domains: value.equivalent_domains.unwrap_or_default(), + global_equivalent_domains: value + .global_equivalent_domains + .unwrap_or_default() + .into_iter() + .map(|s| s.try_into()) + .collect::>>()?, + }) + } +} diff --git a/crates/bitwarden/src/secrets_manager/mod.rs b/crates/bitwarden/src/secrets_manager/mod.rs index 0afbfe38c..181edf6b6 100644 --- a/crates/bitwarden/src/secrets_manager/mod.rs +++ b/crates/bitwarden/src/secrets_manager/mod.rs @@ -1,5 +1,9 @@ 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 index 3ba6ecd36..ab3b7bd62 100644 --- a/crates/bitwarden/src/secrets_manager/projects/create.rs +++ b/crates/bitwarden/src/secrets_manager/projects/create.rs @@ -1,10 +1,14 @@ 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::Result}; +use crate::{ + client::Client, + error::{Error, Result}, +}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -19,12 +23,13 @@ pub(crate) async fn create_project( client: &mut Client, input: &ProjectCreateRequest, ) -> Result { - let enc = client.get_encryption_settings()?; - - let org_id = Some(input.organization_id); + let key = client + .get_encryption_settings()? + .get_key(&Some(input.organization_id)) + .ok_or(Error::VaultLocked)?; let project = Some(ProjectCreateRequestModel { - name: enc.encrypt(input.name.as_bytes(), &org_id)?.to_string(), + name: input.name.clone().encrypt_with_key(key)?.to_string(), }); let config = client.get_api_configurations().await; diff --git a/crates/bitwarden/src/secrets_manager/projects/delete.rs b/crates/bitwarden/src/secrets_manager/projects/delete.rs index 55f792669..05c808c7e 100644 --- a/crates/bitwarden/src/secrets_manager/projects/delete.rs +++ b/crates/bitwarden/src/secrets_manager/projects/delete.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use crate::{ client::Client, - error::{Error, Result}, + error::{require, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -62,7 +62,7 @@ impl ProjectDeleteResponse { response: BulkDeleteResponseModel, ) -> Result { Ok(ProjectDeleteResponse { - id: response.id.ok_or(Error::MissingFields)?, + id: require!(response.id), error: response.error, }) } diff --git a/crates/bitwarden/src/secrets_manager/projects/project_response.rs b/crates/bitwarden/src/secrets_manager/projects/project_response.rs index b8c82806b..af67df9ab 100644 --- a/crates/bitwarden/src/secrets_manager/projects/project_response.rs +++ b/crates/bitwarden/src/secrets_manager/projects/project_response.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::ProjectResponseModel; +use bitwarden_crypto::{Decryptable, EncString}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -6,8 +7,7 @@ use uuid::Uuid; use crate::{ client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString}, - error::{Error, Result}, + error::{require, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -25,27 +25,19 @@ impl ProjectResponse { response: ProjectResponseModel, enc: &EncryptionSettings, ) -> Result { - let organization_id = response.organization_id.ok_or(Error::MissingFields)?; + let organization_id = require!(response.organization_id); - let name = response - .name - .ok_or(Error::MissingFields)? + let name = require!(response.name) .parse::()? .decrypt(enc, &Some(organization_id))?; Ok(ProjectResponse { - id: response.id.ok_or(Error::MissingFields)?, + id: require!(response.id), organization_id, name, - creation_date: response - .creation_date - .ok_or(Error::MissingFields)? - .parse()?, - revision_date: response - .revision_date - .ok_or(Error::MissingFields)? - .parse()?, + creation_date: require!(response.creation_date).parse()?, + revision_date: require!(response.revision_date).parse()?, }) } } diff --git a/crates/bitwarden/src/secrets_manager/projects/update.rs b/crates/bitwarden/src/secrets_manager/projects/update.rs index b0a040c96..e00609ff4 100644 --- a/crates/bitwarden/src/secrets_manager/projects/update.rs +++ b/crates/bitwarden/src/secrets_manager/projects/update.rs @@ -1,10 +1,14 @@ 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::Result}; +use crate::{ + client::Client, + error::{Error, Result}, +}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -21,12 +25,13 @@ pub(crate) async fn update_project( client: &mut Client, input: &ProjectPutRequest, ) -> Result { - let enc = client.get_encryption_settings()?; - - let org_id = Some(input.organization_id); + let key = client + .get_encryption_settings()? + .get_key(&Some(input.organization_id)) + .ok_or(Error::VaultLocked)?; let project = Some(ProjectUpdateRequestModel { - name: enc.encrypt(input.name.as_bytes(), &org_id)?.to_string(), + name: input.name.clone().encrypt_with_key(key)?.to_string(), }); let config = client.get_api_configurations().await; diff --git a/crates/bitwarden/src/secrets_manager/secrets/create.rs b/crates/bitwarden/src/secrets_manager/secrets/create.rs index ddec3abd4..4f84223dc 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/create.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/create.rs @@ -1,10 +1,14 @@ 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::Result, Client}; +use crate::{ + error::{Error, Result}, + Client, +}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -24,14 +28,15 @@ pub(crate) async fn create_secret( client: &mut Client, input: &SecretCreateRequest, ) -> Result { - let enc = client.get_encryption_settings()?; - - let org_id = Some(input.organization_id); + let key = client + .get_encryption_settings()? + .get_key(&Some(input.organization_id)) + .ok_or(Error::VaultLocked)?; let secret = Some(SecretCreateRequestModel { - key: enc.encrypt(input.key.as_bytes(), &org_id)?.to_string(), - value: enc.encrypt(input.value.as_bytes(), &org_id)?.to_string(), - note: enc.encrypt(input.note.as_bytes(), &org_id)?.to_string(), + 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(), }); diff --git a/crates/bitwarden/src/secrets_manager/secrets/delete.rs b/crates/bitwarden/src/secrets_manager/secrets/delete.rs index 19337e7a1..f3fe264e1 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/delete.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/delete.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use crate::{ client::Client, - error::{Error, Result}, + error::{require, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -62,7 +62,7 @@ impl SecretDeleteResponse { response: BulkDeleteResponseModel, ) -> Result { Ok(SecretDeleteResponse { - id: response.id.ok_or(Error::MissingFields)?, + id: require!(response.id), error: response.error, }) } diff --git a/crates/bitwarden/src/secrets_manager/secrets/list.rs b/crates/bitwarden/src/secrets_manager/secrets/list.rs index 1f4ccba3f..48a507667 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/list.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/list.rs @@ -1,14 +1,14 @@ use bitwarden_api_api::models::{ SecretWithProjectsListResponseModel, SecretsWithProjectsInnerSecret, }; +use bitwarden_crypto::{Decryptable, EncString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ client::{encryption_settings::EncryptionSettings, Client}, - crypto::{Decryptable, EncString}, - error::{Error, Result}, + error::{require, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -93,16 +93,14 @@ impl SecretIdentifierResponse { response: SecretsWithProjectsInnerSecret, enc: &EncryptionSettings, ) -> Result { - let organization_id = response.organization_id.ok_or(Error::MissingFields)?; + let organization_id = require!(response.organization_id); - let key = response - .key - .ok_or(Error::MissingFields)? + let key = require!(response.key) .parse::()? .decrypt(enc, &Some(organization_id))?; Ok(SecretIdentifierResponse { - id: response.id.ok_or(Error::MissingFields)?, + id: require!(response.id), organization_id, key, }) diff --git a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs b/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs index a7fe49200..ffaa7f7e0 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs @@ -1,6 +1,7 @@ use bitwarden_api_api::models::{ BaseSecretResponseModel, BaseSecretResponseModelListResponseModel, SecretResponseModel, }; +use bitwarden_crypto::{Decryptable, EncString}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -8,8 +9,7 @@ use uuid::Uuid; use crate::{ client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString}, - error::{Error, Result}, + error::{require, Result}, }; #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -51,19 +51,13 @@ impl SecretResponse { ) -> Result { let org_id = response.organization_id; - let key = response - .key - .ok_or(Error::MissingFields)? + let key = require!(response.key) .parse::()? .decrypt(enc, &org_id)?; - let value = response - .value - .ok_or(Error::MissingFields)? + let value = require!(response.value) .parse::()? .decrypt(enc, &org_id)?; - let note = response - .note - .ok_or(Error::MissingFields)? + let note = require!(response.note) .parse::()? .decrypt(enc, &org_id)?; @@ -73,21 +67,15 @@ impl SecretResponse { .and_then(|p| p.id); Ok(SecretResponse { - id: response.id.ok_or(Error::MissingFields)?, - organization_id: org_id.ok_or(Error::MissingFields)?, + id: require!(response.id), + organization_id: require!(org_id), project_id: project, key, value, note, - creation_date: response - .creation_date - .ok_or(Error::MissingFields)? - .parse()?, - revision_date: response - .revision_date - .ok_or(Error::MissingFields)? - .parse()?, + creation_date: require!(response.creation_date).parse()?, + revision_date: require!(response.revision_date).parse()?, }) } } diff --git a/crates/bitwarden/src/secrets_manager/secrets/update.rs b/crates/bitwarden/src/secrets_manager/secrets/update.rs index b1a82bad8..f9e54f810 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/update.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/update.rs @@ -1,10 +1,14 @@ 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::Result}; +use crate::{ + client::Client, + error::{Error, Result}, +}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -24,14 +28,15 @@ pub(crate) async fn update_secret( client: &mut Client, input: &SecretPutRequest, ) -> Result { - let enc = client.get_encryption_settings()?; - - let org_id = Some(input.organization_id); + let key = client + .get_encryption_settings()? + .get_key(&Some(input.organization_id)) + .ok_or(Error::VaultLocked)?; let secret = Some(SecretUpdateRequestModel { - key: enc.encrypt(input.key.as_bytes(), &org_id)?.to_string(), - value: enc.encrypt(input.value.as_bytes(), &org_id)?.to_string(), - note: enc.encrypt(input.note.as_bytes(), &org_id)?.to_string(), + 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(), }); diff --git a/crates/bitwarden/src/secrets_manager/state.rs b/crates/bitwarden/src/secrets_manager/state.rs new file mode 100644 index 000000000..c677ab7e9 --- /dev/null +++ b/crates/bitwarden/src/secrets_manager/state.rs @@ -0,0 +1,53 @@ +use std::{fmt::Debug, path::Path}; + +use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable, SensitiveString}; +use serde::{Deserialize, Serialize}; + +use crate::{ + auth::AccessToken, + error::{Error, Result}, +}; + +const STATE_VERSION: u32 = 1; + +#[cfg(feature = "secrets")] +#[derive(Serialize, Deserialize, Debug)] +pub struct ClientState { + pub(crate) version: u32, + pub(crate) token: String, + pub(crate) encryption_key: SensitiveString, +} + +impl ClientState { + pub fn new(token: String, encryption_key: SensitiveString) -> Self { + Self { + version: STATE_VERSION, + token, + encryption_key, + } + } +} + +pub fn get(state_file: &Path, access_token: &AccessToken) -> Result { + let file_content = std::fs::read_to_string(state_file)?; + + let encrypted_state: EncString = file_content.parse()?; + let decrypted_state: String = encrypted_state.decrypt_with_key(&access_token.encryption_key)?; + let client_state: ClientState = serde_json::from_str(&decrypted_state)?; + + if client_state.version != STATE_VERSION { + return Err(Error::InvalidStateFileVersion); + } + + Ok(client_state) +} + +pub fn set(state_file: &Path, access_token: &AccessToken, state: ClientState) -> Result<()> { + let serialized_state: String = serde_json::to_string(&state)?; + let encrypted_state: EncString = + serialized_state.encrypt_with_key(&access_token.encryption_key)?; + let state_string: String = encrypted_state.to_string(); + + std::fs::write(state_file, state_string) + .map_err(|_| "Failure writing to the state file.".into()) +} diff --git a/crates/bitwarden/src/tool/client_generator.rs b/crates/bitwarden/src/tool/client_generator.rs new file mode 100644 index 000000000..16c786f9b --- /dev/null +++ b/crates/bitwarden/src/tool/client_generator.rs @@ -0,0 +1,92 @@ +use bitwarden_generators::{passphrase, password, username}; + +use crate::{ + error::Result, + generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest}, + Client, +}; + +pub struct ClientGenerator<'a> { + pub(crate) client: &'a crate::Client, +} + +impl<'a> ClientGenerator<'a> { + /// Generates a random password. + /// + /// The character sets and password length can be customized using the `input` parameter. + /// + /// # Examples + /// + /// ``` + /// use bitwarden::{Client, generators::PasswordGeneratorRequest, error::Result}; + /// async fn test() -> Result<()> { + /// let input = PasswordGeneratorRequest { + /// lowercase: true, + /// uppercase: true, + /// numbers: true, + /// length: 20, + /// ..Default::default() + /// }; + /// let password = Client::new(None).generator().password(input).await.unwrap(); + /// println!("{}", password); + /// Ok(()) + /// } + /// ``` + pub async fn password(&self, input: PasswordGeneratorRequest) -> Result { + Ok(password(input)?) + } + + /// Generates a random passphrase. + /// A passphrase is a combination of random words separated by a character. + /// An example of passphrase is `correct horse battery staple`. + /// + /// The number of words and their case, the word separator, and the inclusion of + /// a number in the passphrase can be customized using the `input` parameter. + /// + /// # Examples + /// + /// ``` + /// use bitwarden::{Client, generators::PassphraseGeneratorRequest, error::Result}; + /// async fn test() -> Result<()> { + /// let input = PassphraseGeneratorRequest { + /// num_words: 4, + /// ..Default::default() + /// }; + /// let passphrase = Client::new(None).generator().passphrase(input).await.unwrap(); + /// println!("{}", passphrase); + /// Ok(()) + /// } + /// ``` + pub async fn passphrase(&self, input: PassphraseGeneratorRequest) -> Result { + Ok(passphrase(input)?) + } + + /// Generates a random username. + /// There are different username generation strategies, which can be customized using the + /// `input` parameter. + /// + /// Note that most generation strategies will be executed on the client side, but `Forwarded` + /// will use third-party services, which may require a specific setup or API key. + /// + /// ``` + /// use bitwarden::{Client, generators::{UsernameGeneratorRequest}, error::Result}; + /// async fn test() -> Result<()> { + /// let input = UsernameGeneratorRequest::Word { + /// capitalize: true, + /// include_number: true, + /// }; + /// let username = Client::new(None).generator().username(input).await.unwrap(); + /// println!("{}", username); + /// Ok(()) + /// } + /// ``` + pub async fn username(&self, input: UsernameGeneratorRequest) -> Result { + Ok(username(input, self.client.get_http_client()).await?) + } +} + +impl<'a> Client { + pub fn generator(&'a self) -> ClientGenerator<'a> { + ClientGenerator { client: self } + } +} diff --git a/crates/bitwarden/src/tool/exporters/client_exporter.rs b/crates/bitwarden/src/tool/exporters/client_exporter.rs index f14cbc846..05eb737f3 100644 --- a/crates/bitwarden/src/tool/exporters/client_exporter.rs +++ b/crates/bitwarden/src/tool/exporters/client_exporter.rs @@ -6,17 +6,18 @@ use crate::{ }; pub struct ClientExporters<'a> { - pub(crate) _client: &'a crate::Client, + 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(folders, ciphers, format) + export_vault(self.client, folders, ciphers, format) } pub async fn export_organization_vault( @@ -31,6 +32,6 @@ impl<'a> ClientExporters<'a> { impl<'a> Client { pub fn exporters(&'a self) -> ClientExporters<'a> { - ClientExporters { _client: self } + ClientExporters { client: self } } } diff --git a/crates/bitwarden/src/tool/exporters/mod.rs b/crates/bitwarden/src/tool/exporters/mod.rs index 508aae8fb..2da6ac833 100644 --- a/crates/bitwarden/src/tool/exporters/mod.rs +++ b/crates/bitwarden/src/tool/exporters/mod.rs @@ -1,27 +1,73 @@ +use bitwarden_crypto::Decryptable; +use bitwarden_exporters::export; use schemars::JsonSchema; use crate::{ - error::Result, - vault::{Cipher, Collection, Folder}, + client::{LoginMethod, UserLoginMethod}, + error::{require, Error, Result}, + vault::{ + login::LoginUriView, Cipher, CipherType, CipherView, Collection, FieldView, Folder, + FolderView, SecureNoteType, + }, + Client, }; mod client_exporter; +pub use client_exporter::ClientExporters; #[derive(JsonSchema)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] pub enum ExportFormat { Csv, Json, - AccountEncryptedJson, // TODO: Should we deprecate this option completely? EncryptedJson { password: String }, } pub(super) fn export_vault( - _folders: Vec, - _ciphers: Vec, - _format: ExportFormat, + client: &Client, + folders: Vec, + ciphers: Vec, + format: ExportFormat, ) -> Result { - todo!(); + let enc = client.get_encryption_settings()?; + + let folders: Vec = folders.decrypt(enc, &None)?; + let folders: Vec = + folders.into_iter().flat_map(|f| f.try_into()).collect(); + + let ciphers: Vec = ciphers.decrypt(enc, &None)?; + 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( @@ -31,3 +77,255 @@ pub(super) fn export_organization_vault( ) -> Result { todo!(); } + +impl TryFrom for bitwarden_exporters::Folder { + type Error = Error; + + fn try_from(value: FolderView) -> Result { + Ok(Self { + id: require!(value.id), + name: value.name, + }) + } +} + +impl TryFrom for bitwarden_exporters::Cipher { + type Error = Error; + + 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 { + username: l.username, + password: l.password, + login_uris: l + .uris + .unwrap_or_default() + .into_iter() + .map(|u| u.into()) + .collect(), + 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::Card => { + let c = require!(value.card); + bitwarden_exporters::CipherType::Card(Box::new(bitwarden_exporters::Card { + cardholder_name: c.cardholder_name, + exp_month: c.exp_month, + exp_year: c.exp_year, + code: c.code, + brand: c.brand, + number: c.number, + })) + } + CipherType::Identity => { + let i = require!(value.identity); + bitwarden_exporters::CipherType::Identity(Box::new(bitwarden_exporters::Identity { + title: i.title, + first_name: i.first_name, + middle_name: i.middle_name, + last_name: i.last_name, + address1: i.address1, + address2: i.address2, + address3: i.address3, + city: i.city, + state: i.state, + postal_code: i.postal_code, + country: i.country, + company: i.company, + email: i.email, + phone: i.phone, + ssn: i.ssn, + username: i.username, + passport_number: i.passport_number, + license_number: i.license_number, + })) + } + }; + + Ok(Self { + id: require!(value.id), + folder_id: value.folder_id, + name: value.name, + notes: value.notes, + r#type: r, + favorite: value.favorite, + reprompt: value.reprompt as u8, + fields: value + .fields + .unwrap_or_default() + .into_iter() + .map(|f| f.into()) + .collect(), + revision_date: value.revision_date, + creation_date: value.creation_date, + deleted_date: value.deleted_date, + }) + } +} + +impl From for bitwarden_exporters::Field { + fn from(value: FieldView) -> Self { + Self { + name: value.name, + value: value.value, + r#type: value.r#type as u8, + linked_id: value.linked_id.map(|id| id.into()), + } + } +} + +impl From for bitwarden_exporters::LoginUri { + fn from(value: LoginUriView) -> Self { + Self { + r#match: value.r#match.map(|v| v as u8), + uri: value.uri, + } + } +} + +impl From for bitwarden_exporters::SecureNoteType { + fn from(value: SecureNoteType) -> Self { + match value { + SecureNoteType::Generic => bitwarden_exporters::SecureNoteType::Generic, + } + } +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use bitwarden_crypto::Kdf; + use chrono::{DateTime, Utc}; + + use super::*; + use crate::vault::{login::LoginView, CipherRepromptType}; + + #[test] + fn test_try_from_folder_view() { + let view = FolderView { + id: Some("fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap()), + name: "test_name".to_string(), + revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(), + }; + + let f: bitwarden_exporters::Folder = view.try_into().unwrap(); + + assert_eq!( + f.id, + "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap() + ); + assert_eq!(f.name, "test_name".to_string()); + } + + #[test] + fn test_try_from_cipher_view_login() { + let cipher_view = CipherView { + r#type: CipherType::Login, + login: Some(LoginView { + username: Some("test_username".to_string()), + password: Some("test_password".to_string()), + password_revision_date: None, + uris: None, + totp: None, + autofill_on_page_load: None, + fido2_credentials: None, + }), + id: "fd411a1a-fec8-4070-985d-0e6560860e69".parse().ok(), + organization_id: None, + folder_id: None, + collection_ids: vec![], + key: None, + name: "My login".to_string(), + notes: None, + identity: None, + card: None, + secure_note: None, + favorite: false, + reprompt: CipherRepromptType::None, + organization_use_totp: true, + 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 cipher: bitwarden_exporters::Cipher = cipher_view.try_into().unwrap(); + + assert_eq!( + cipher.id, + "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap() + ); + assert_eq!(cipher.folder_id, None); + assert_eq!(cipher.name, "My login".to_string()); + assert_eq!(cipher.notes, None); + assert!(!cipher.favorite); + assert_eq!(cipher.reprompt, 0); + assert!(cipher.fields.is_empty()); + assert_eq!( + cipher.revision_date, + "2024-01-30T17:55:36.150Z".parse::>().unwrap() + ); + assert_eq!( + cipher.creation_date, + "2024-01-30T17:55:36.150Z".parse::>().unwrap() + ); + assert_eq!(cipher.deleted_date, None); + + if let bitwarden_exporters::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()); + assert_eq!(l.totp, None); + } else { + 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/src/tool/generators/client_generator.rs b/crates/bitwarden/src/tool/generators/client_generator.rs deleted file mode 100644 index 0384eec6a..000000000 --- a/crates/bitwarden/src/tool/generators/client_generator.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::{ - error::Result, - tool::generators::password::{ - passphrase, password, PassphraseGeneratorRequest, PasswordGeneratorRequest, - }, - Client, -}; - -pub struct ClientGenerator<'a> { - pub(crate) _client: &'a crate::Client, -} - -impl<'a> ClientGenerator<'a> { - pub async fn password(&self, input: PasswordGeneratorRequest) -> Result { - password(input) - } - - pub async fn passphrase(&self, input: PassphraseGeneratorRequest) -> Result { - passphrase(input) - } -} - -impl<'a> Client { - pub fn generator(&'a self) -> ClientGenerator<'a> { - ClientGenerator { _client: self } - } -} diff --git a/crates/bitwarden/src/tool/generators/mod.rs b/crates/bitwarden/src/tool/generators/mod.rs deleted file mode 100644 index bdc0fb260..000000000 --- a/crates/bitwarden/src/tool/generators/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod client_generator; -mod password; - -pub use password::{PassphraseGeneratorRequest, PasswordGeneratorRequest}; diff --git a/crates/bitwarden/src/tool/generators/password.rs b/crates/bitwarden/src/tool/generators/password.rs deleted file mode 100644 index 0a3874082..000000000 --- a/crates/bitwarden/src/tool/generators/password.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::error::Result; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// Password generator request. If all options are false, the default is to -/// generate a password with: -/// - lowercase -/// - uppercase -/// - numbers -/// -/// The default length is 16. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -#[cfg_attr(feature = "mobile", derive(uniffi::Record))] -pub struct PasswordGeneratorRequest { - pub lowercase: bool, - pub uppercase: bool, - pub numbers: bool, - pub special: bool, - - pub length: Option, - - pub avoid_ambiguous: Option, // TODO: Should we rename this to include_all_characters? - pub min_lowercase: Option, - pub min_uppercase: Option, - pub min_number: Option, - pub min_special: Option, -} - -/// Passphrase generator request. -/// -/// The default separator is `-` and default number of words is 3. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Default)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -#[cfg_attr(feature = "mobile", derive(uniffi::Record))] -pub struct PassphraseGeneratorRequest { - pub num_words: Option, - pub word_separator: Option, - pub capitalize: Option, - pub include_number: Option, -} - -pub(super) fn password(_input: PasswordGeneratorRequest) -> Result { - Ok("pa11w0rd".to_string()) -} - -pub(super) fn passphrase(_input: PassphraseGeneratorRequest) -> Result { - Ok("correct-horse-battery-staple".to_string()) -} diff --git a/crates/bitwarden/src/tool/mod.rs b/crates/bitwarden/src/tool/mod.rs index 2130a6b0c..a94528163 100644 --- a/crates/bitwarden/src/tool/mod.rs +++ b/crates/bitwarden/src/tool/mod.rs @@ -1,5 +1,4 @@ mod exporters; -mod generators; - -pub use exporters::ExportFormat; -pub use generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest}; +pub use exporters::{ClientExporters, ExportFormat}; +mod client_generator; +pub use client_generator::ClientGenerator; diff --git a/crates/bitwarden/src/uniffi_support.rs b/crates/bitwarden/src/uniffi_support.rs index 701f142b9..bf1eade70 100644 --- a/crates/bitwarden/src/uniffi_support.rs +++ b/crates/bitwarden/src/uniffi_support.rs @@ -1,36 +1,22 @@ -use std::{num::NonZeroU32, str::FromStr}; +use std::num::NonZeroU32; +use bitwarden_crypto::{AsymmetricEncString, EncString, SensitiveString}; use uuid::Uuid; -use crate::{crypto::EncString, error::Error, UniffiCustomTypeConverter}; - -uniffi::custom_type!(NonZeroU32, u32); - -impl UniffiCustomTypeConverter for NonZeroU32 { - type Builtin = u32; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::new(val).ok_or(Error::Internal("Number is zero").into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.get() - } -} - -uniffi::custom_type!(EncString, String); - -impl UniffiCustomTypeConverter for EncString { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::from_str(&val).map_err(|e| e.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} +use crate::UniffiCustomTypeConverter; + +uniffi::ffi_converter_forward!(NonZeroU32, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!( + AsymmetricEncString, + bitwarden_crypto::UniFfiTag, + crate::UniFfiTag +); +uniffi::ffi_converter_forward!( + SensitiveString, + bitwarden_crypto::UniFfiTag, + crate::UniFfiTag +); type DateTime = chrono::DateTime; uniffi::custom_type!(DateTime, std::time::SystemTime); diff --git a/crates/bitwarden/src/util.rs b/crates/bitwarden/src/util.rs index fc9f30447..aaf47a1a6 100644 --- a/crates/bitwarden/src/util.rs +++ b/crates/bitwarden/src/util.rs @@ -1,85 +1,13 @@ -use std::num::NonZeroU32; - use base64::{ alphabet, engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}, - Engine, }; -use crate::error::Result; - -pub fn default_pbkdf2_iterations() -> NonZeroU32 { - NonZeroU32::new(600_000).unwrap() -} -#[cfg(feature = "internal")] -pub fn default_argon2_iterations() -> NonZeroU32 { - NonZeroU32::new(3).unwrap() -} -#[cfg(feature = "internal")] -pub fn default_argon2_memory() -> NonZeroU32 { - NonZeroU32::new(64).unwrap() -} -#[cfg(feature = "internal")] -pub fn default_argon2_parallelism() -> NonZeroU32 { - NonZeroU32::new(4).unwrap() -} - -#[derive(serde::Deserialize)] -pub struct JWTToken { - pub sub: String, - pub email: Option, - pub organization: Option, - pub scope: Vec, -} - -const BASE64_ENGINE_CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new() - .with_encode_padding(true) - .with_decode_padding_mode(DecodePaddingMode::Indifferent); +const INDIFFERENT: GeneralPurposeConfig = + GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent); -pub const BASE64_ENGINE: GeneralPurpose = - GeneralPurpose::new(&alphabet::STANDARD, BASE64_ENGINE_CONFIG); - -pub fn decode_token(token: &str) -> Result { - let split = token.split('.').collect::>(); - if split.len() != 3 { - return Err(crate::error::Error::Internal( - "JWT token has an invalid number of parts", - )); - } - let decoded = BASE64_ENGINE.decode(split[1])?; - Ok(serde_json::from_slice(&decoded)?) -} - -#[cfg(test)] -mod tests { - #[test] - fn can_decode_jwt() { - let jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjMwMURENkE1MEU4NEUxRDA5MUM4MUQzQjAwQkY5MDEwQz\ - g1REJEOUFSUzI1NiIsInR5cCI6ImF0K2p3dCIsIng1dCI6Ik1CM1dwUTZFNGRDUnlCMDdBTC1RRU1oZHZabyJ9.eyJu\ - YmYiOjE2NzUxMDM1NzcsImV4cCI6MTY3NTEwNzE3NywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsImNsaWVudF9pZCI\ - 6IndlYiIsInN1YiI6ImUyNWQzN2YzLWI2MDMtNDBkZS04NGJhLWFmOTYwMTJmNWE0MiIsImF1dGhfdGltZSI6MTY3NT\ - EwMzU0OSwiaWRwIjoiYml0d2FyZGVuIiwicHJlbWl1bSI6ZmFsc2UsImVtYWlsIjoidGVzdEBiaXR3YXJkZW4uY29tI\ - iwiZW1haWxfdmVyaWZpZWQiOnRydWUsInNzdGFtcCI6IkUzNElDWVhRUFRDS01EVldBREZDNktHNDJCQldJRDdJIiwi\ - bmFtZSI6IlRlc3QiLCJvcmdvd25lciI6ImY0ZTQ0YTdmLTExOTAtNDMyYS05ZDRhLWFmOTYwMTMxMjdjYiIsImRldml\ - jZSI6Ijg5Mjg5M2FiLWRkNDMtNDUwYS04NGI1LWFhOWM1YjdiYjJkOCIsImp0aSI6IkEzMkVFNjY5NDdEQzlDNUE2MT\ - IwRURBRTIwNzc5OUJFIiwiaWF0IjoxNjc1MTAzNTc3LCJzY29wZSI6WyJhcGkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhb\ - XIiOlsiQXBwbGljYXRpb24iXX0.AyDkKvjmyaSPQViQSa2sGTKIkDGrUAtDmwpE57K4DDWT0QvwDe7FMktmwiF4LH36\ - wx_FnpH21VI1pzwJeTHXtaz3niANJtQZjzGFsNAna_95vrsxZC2YizgGlt6mX4YIGmAw9DiYrmaN0BvQOEm_caV_u6f\ - a30iz9Kvjxf7cpzeZvPEysxGpB3k3TRYTkFUdV43HiXdhXMBhyyOpFU6Fk6yA41y7-8bGYc5mYGknWktmPD9Yx-1xKL\ - ftFja1SnCoLPWvDeK60lqWZQiT4tZHCYJ7m0bBNCccYHc2Kk2Bo5-UoyDxazPwsqMxeNfjlaUuj3o5N_uQ-4n_gVbeA\ - qWV2wrel5UhYjWnczMSLBtt9p0W35kkBPt3ZAnRWMtQMPNH04p-_L6cG-Xu6lDksBTwaavcmtnCKG8V91826EiQ8MrF\ - wGWQRZV6tPKTDAYCgSAZGBY3QDmPGT5BeFcg5Ag_nYYIIifKP-kv10v_N-TOcT3NeGBOUlAZ-9m7iT7Rk3vC--SDZdA\ - U5turoBFiiPL2XXfAjM7P0r7J91gfXc0FaD6I2jDxOmym5h7Yn5phLsbC2NlIXkZp54dKHICenPl4ve6ndDIJacVeS5\ - f3LEddAPV8cAFza4DjA8pZJLFrMyRvMXcL_PjKF8qPVzqVWh03lfJ4clOIxR2gOuWIc902Y5E"; - - let token = super::decode_token(jwt).unwrap(); - assert_eq!(token.sub, "e25d37f3-b603-40de-84ba-af96012f5a42"); - assert_eq!(token.email.as_deref(), Some("test@bitwarden.com")); - assert_eq!(token.organization.as_deref(), None); - assert_eq!(token.scope[0], "api"); - assert_eq!(token.scope[1], "offline_access"); - } -} +pub const STANDARD_INDIFFERENT: GeneralPurpose = + GeneralPurpose::new(&alphabet::STANDARD, INDIFFERENT); #[cfg(test)] pub async fn start_mock(mocks: Vec) -> (wiremock::MockServer, crate::Client) { diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden/src/vault/cipher/attachment.rs index 143e2a4f9..a573e92b0 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden/src/vault/cipher/attachment.rs @@ -1,12 +1,11 @@ +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, -}; +use super::Cipher; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -30,31 +29,176 @@ pub struct AttachmentView { pub size: Option, pub size_name: Option, pub file_name: Option, - pub key: Option, + pub key: Option, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct AttachmentEncryptResult { + pub attachment: Attachment, + pub contents: Vec, +} + +pub struct AttachmentFile { + pub cipher: Cipher, + pub attachment: Attachment, + pub contents: EncString, } -impl Encryptable for AttachmentView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +pub struct AttachmentFileView<'a> { + pub cipher: Cipher, + pub attachment: AttachmentView, + pub contents: &'a [u8], +} + +impl<'a> KeyEncryptable for AttachmentFileView<'a> { + fn encrypt_with_key( + self, + key: &SymmetricCryptoKey, + ) -> Result { + let ciphers_key = Cipher::get_cipher_key(key, &self.cipher.key)?; + let ciphers_key = ciphers_key.as_ref().unwrap_or(key); + + let mut attachment = self.attachment; + + // Because this is a new attachment, we have to generate a key for it, encrypt the contents + // with it, and then encrypt the key with the cipher key + let attachment_key = SymmetricCryptoKey::generate(rand::thread_rng()); + let encrypted_contents = self.contents.encrypt_with_key(&attachment_key)?; + attachment.key = Some( + attachment_key + .to_vec() + .expose() + .encrypt_with_key(ciphers_key)?, + ); + + Ok(AttachmentEncryptResult { + attachment: attachment.encrypt_with_key(ciphers_key)?, + contents: encrypted_contents.to_buffer()?, + }) + } +} + +impl KeyDecryptable> for AttachmentFile { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result, CryptoError> { + let ciphers_key = Cipher::get_cipher_key(key, &self.cipher.key)?; + let ciphers_key = ciphers_key.as_ref().unwrap_or(key); + + let mut attachment_key: Vec = self + .attachment + .key + .as_ref() + .ok_or(CryptoError::MissingKey)? + .decrypt_with_key(ciphers_key)?; + let attachment_key = SymmetricCryptoKey::try_from(attachment_key.as_mut_slice())?; + + self.contents.decrypt_with_key(&attachment_key) + } +} + +impl KeyEncryptable for AttachmentView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Attachment { id: self.id, url: self.url, size: self.size, size_name: self.size_name, - file_name: self.file_name.encrypt(enc, org_id)?, - key: self.key.encrypt(enc, org_id)?, + file_name: self.file_name.encrypt_with_key(key)?, + key: self.key, }) } } -impl Decryptable for Attachment { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for Attachment { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(AttachmentView { id: self.id.clone(), url: self.url.clone(), size: self.size.clone(), size_name: self.size_name.clone(), - file_name: self.file_name.decrypt(enc, org_id)?, - key: self.key.decrypt(enc, org_id)?, + file_name: self.file_name.decrypt_with_key(key)?, + key: self.key.clone(), + }) + } +} + +impl TryFrom for Attachment { + type Error = Error; + + fn try_from(attachment: bitwarden_api_api::models::AttachmentResponseModel) -> Result { + Ok(Self { + id: attachment.id, + url: attachment.url, + size: attachment.size, + size_name: attachment.size_name, + file_name: EncString::try_from_optional(attachment.file_name)?, + key: EncString::try_from_optional(attachment.key)?, }) } } + +#[cfg(test)] +mod tests { + use base64::{engine::general_purpose::STANDARD, Engine}; + use bitwarden_crypto::{EncString, KeyDecryptable, SensitiveString, SymmetricCryptoKey}; + + use crate::vault::{ + cipher::cipher::{CipherRepromptType, CipherType}, + Attachment, AttachmentFile, Cipher, + }; + + #[test] + fn test_attachment_key() { + let user_key : SymmetricCryptoKey = SensitiveString::test("w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q==").try_into().unwrap(); + + let attachment = Attachment { + id: None, + url: None, + size: Some("161".into()), + size_name: Some("161 Bytes".into()), + file_name: Some("2.M3z1MOO9eBG9BWRTEUbPog==|jPw0By1AakHDfoaY8UOwOQ==|eP9/J1583OJpHsSM4ZnXZzdBHfqVTXnOXGlkkmAKSfA=".parse().unwrap()), + key: Some("2.r288/AOSPiaLFkW07EBGBw==|SAmnnCbOLFjX5lnURvoualOetQwuyPc54PAmHDTRrhT0gwO9ailna9U09q9bmBfI5XrjNNEsuXssgzNygRkezoVQvZQggZddOwHB6KQW5EQ=|erIMUJp8j+aTcmhdE50zEX+ipv/eR1sZ7EwULJm/6DY=".parse().unwrap()) + }; + + let 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(), + }; + + let enc_file = STANDARD.decode(b"Ao00qr1xLsV+ZNQpYZ/UwEwOWo3hheKwCYcOGIbsorZ6JIG2vLWfWEXCVqP0hDuzRvmx8otApNZr8pJYLNwCe1aQ+ySHQYGkdubFjoMojulMbQ959Y4SJ6Its/EnVvpbDnxpXTDpbutDxyhxfq1P3lstL2G9rObJRrxiwdGlRGu1h94UA1fCCkIUQux5LcqUee6W4MyQmRnsUziH8gGzmtI=").unwrap(); + let original = STANDARD.decode(b"rMweTemxOL9D0iWWfRxiY3enxiZ5IrwWD6ef2apGO6MvgdGhy2fpwmATmn7BpSj9lRumddLLXm7u8zSp6hnXt1hS71YDNh78LjGKGhGL4sbg8uNnpa/I6GK/83jzqGYN7+ESbg==").unwrap(); + + let dec = AttachmentFile { + cipher, + attachment, + contents: EncString::from_buffer(&enc_file).unwrap(), + } + .decrypt_with_key(&user_key) + .unwrap(); + + assert_eq!(dec, original); + } +} diff --git a/crates/bitwarden/src/vault/cipher/card.rs b/crates/bitwarden/src/vault/cipher/card.rs index 9636ba83f..cd61a17d8 100644 --- a/crates/bitwarden/src/vault/cipher/card.rs +++ b/crates/bitwarden/src/vault/cipher/card.rs @@ -1,12 +1,11 @@ +use bitwarden_api_api::models::CipherCardModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -32,28 +31,43 @@ pub struct CardView { pub number: Option, } -impl Encryptable for CardView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyEncryptable for CardView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Card { - cardholder_name: self.cardholder_name.encrypt(enc, org_id)?, - exp_month: self.exp_month.encrypt(enc, org_id)?, - exp_year: self.exp_year.encrypt(enc, org_id)?, - code: self.code.encrypt(enc, org_id)?, - brand: self.brand.encrypt(enc, org_id)?, - number: self.number.encrypt(enc, org_id)?, + cardholder_name: self.cardholder_name.encrypt_with_key(key)?, + exp_month: self.exp_month.encrypt_with_key(key)?, + exp_year: self.exp_year.encrypt_with_key(key)?, + code: self.code.encrypt_with_key(key)?, + brand: self.brand.encrypt_with_key(key)?, + number: self.number.encrypt_with_key(key)?, }) } } -impl Decryptable for Card { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for Card { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(CardView { - cardholder_name: self.cardholder_name.decrypt(enc, org_id)?, - exp_month: self.exp_month.decrypt(enc, org_id)?, - exp_year: self.exp_year.decrypt(enc, org_id)?, - code: self.code.decrypt(enc, org_id)?, - brand: self.brand.decrypt(enc, org_id)?, - number: self.number.decrypt(enc, org_id)?, + cardholder_name: self.cardholder_name.decrypt_with_key(key).ok().flatten(), + exp_month: self.exp_month.decrypt_with_key(key).ok().flatten(), + exp_year: self.exp_year.decrypt_with_key(key).ok().flatten(), + code: self.code.decrypt_with_key(key).ok().flatten(), + brand: self.brand.decrypt_with_key(key).ok().flatten(), + number: self.number.decrypt_with_key(key).ok().flatten(), + }) + } +} + +impl TryFrom for Card { + type Error = Error; + + fn try_from(card: CipherCardModel) -> Result { + Ok(Self { + cardholder_name: EncString::try_from_optional(card.cardholder_name)?, + exp_month: EncString::try_from_optional(card.exp_month)?, + exp_year: EncString::try_from_optional(card.exp_year)?, + code: EncString::try_from_optional(card.code)?, + brand: EncString::try_from_optional(card.brand)?, + number: EncString::try_from_optional(card.number)?, }) } } diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden/src/vault/cipher/cipher.rs index 9bf4bbd25..f245fdc3a 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden/src/vault/cipher/cipher.rs @@ -1,3 +1,8 @@ +use bitwarden_api_api::models::CipherDetailsResponseModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyContainer, KeyDecryptable, KeyEncryptable, LocateKey, SensitiveVec, + SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -10,9 +15,7 @@ use super::{ login, secure_note, }; use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, + error::{require, Error, Result}, vault::password_history, }; @@ -43,6 +46,10 @@ pub struct Cipher { pub folder_id: Option, pub collection_ids: Vec, + /// More recent ciphers uses individual encryption keys to encrypt the other fields of the + /// Cipher. + pub key: Option, + pub name: EncString, pub notes: Option, @@ -77,6 +84,8 @@ pub struct CipherView { pub folder_id: Option, pub collection_ids: Vec, + pub key: Option, + pub name: String, pub notes: Option, @@ -129,30 +138,38 @@ pub struct CipherListView { pub revision_date: DateTime, } -impl Encryptable for CipherView { - fn encrypt(self, enc: &EncryptionSettings, _: &Option) -> Result { - let org_id = &self.organization_id; +impl KeyEncryptable for CipherView { + fn encrypt_with_key(mut self, key: &SymmetricCryptoKey) -> Result { + let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; + let key = ciphers_key.as_ref().unwrap_or(key); + + // For compatibility reasons, we only create checksums for ciphers that have a key + if ciphers_key.is_some() { + self.generate_checksums(); + } + Ok(Cipher { id: self.id, organization_id: self.organization_id, folder_id: self.folder_id, collection_ids: self.collection_ids, - name: self.name.encrypt(enc, org_id)?, - notes: self.notes.encrypt(enc, org_id)?, + key: self.key, + name: self.name.encrypt_with_key(key)?, + notes: self.notes.encrypt_with_key(key)?, r#type: self.r#type, - login: self.login.encrypt(enc, org_id)?, - identity: self.identity.encrypt(enc, org_id)?, - card: self.card.encrypt(enc, org_id)?, - secure_note: self.secure_note.encrypt(enc, org_id)?, + login: self.login.encrypt_with_key(key)?, + identity: self.identity.encrypt_with_key(key)?, + card: self.card.encrypt_with_key(key)?, + secure_note: self.secure_note.encrypt_with_key(key)?, favorite: self.favorite, reprompt: self.reprompt, organization_use_totp: self.organization_use_totp, edit: self.edit, view_password: self.view_password, - local_data: self.local_data.encrypt(enc, org_id)?, - attachments: self.attachments.encrypt(enc, org_id)?, - fields: self.fields.encrypt(enc, org_id)?, - password_history: self.password_history.encrypt(enc, org_id)?, + local_data: self.local_data.encrypt_with_key(key)?, + attachments: self.attachments.encrypt_with_key(key)?, + fields: self.fields.encrypt_with_key(key)?, + password_history: self.password_history.encrypt_with_key(key)?, creation_date: self.creation_date, deleted_date: self.deleted_date, revision_date: self.revision_date, @@ -160,49 +177,72 @@ impl Encryptable for CipherView { } } -impl Decryptable for Cipher { - fn decrypt(&self, enc: &EncryptionSettings, _: &Option) -> Result { - let org_id = &self.organization_id; - Ok(CipherView { +impl KeyDecryptable for Cipher { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; + let key = ciphers_key.as_ref().unwrap_or(key); + + let mut cipher = CipherView { id: self.id, organization_id: self.organization_id, folder_id: self.folder_id, collection_ids: self.collection_ids.clone(), - name: self.name.decrypt(enc, org_id)?, - notes: self.notes.decrypt(enc, org_id)?, + key: self.key.clone(), + name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), + notes: self.notes.decrypt_with_key(key).ok().flatten(), r#type: self.r#type, - login: self.login.decrypt(enc, org_id)?, - identity: self.identity.decrypt(enc, org_id)?, - card: self.card.decrypt(enc, org_id)?, - secure_note: self.secure_note.decrypt(enc, org_id)?, + login: self.login.decrypt_with_key(key).ok().flatten(), + identity: self.identity.decrypt_with_key(key).ok().flatten(), + card: self.card.decrypt_with_key(key).ok().flatten(), + secure_note: self.secure_note.decrypt_with_key(key).ok().flatten(), favorite: self.favorite, reprompt: self.reprompt, organization_use_totp: self.organization_use_totp, edit: self.edit, view_password: self.view_password, - local_data: self.local_data.decrypt(enc, org_id)?, - attachments: self.attachments.decrypt(enc, org_id)?, - fields: self.fields.decrypt(enc, org_id)?, - password_history: self.password_history.decrypt(enc, org_id)?, + local_data: self.local_data.decrypt_with_key(key).ok().flatten(), + attachments: self.attachments.decrypt_with_key(key).ok().flatten(), + fields: self.fields.decrypt_with_key(key).ok().flatten(), + password_history: self.password_history.decrypt_with_key(key).ok().flatten(), creation_date: self.creation_date, deleted_date: self.deleted_date, revision_date: self.revision_date, - }) + }; + + // For compatibility we only remove URLs with invalid checksums if the cipher has a key + if ciphers_key.is_some() { + cipher.remove_invalid_checksums(); + } + + Ok(cipher) } } impl Cipher { - fn get_decrypted_subtitle( - &self, - enc: &EncryptionSettings, - org_id: &Option, - ) -> Result { + /// Get the decrypted individual encryption key for this cipher. + /// Note that some ciphers do not have individual encryption keys, + /// in which case this will return Ok(None) and the key associated + /// with this cipher's user or organization must be used instead + pub(super) fn get_cipher_key( + key: &SymmetricCryptoKey, + ciphers_key: &Option, + ) -> Result, CryptoError> { + ciphers_key + .as_ref() + .map(|k| { + let mut key: Vec = k.decrypt_with_key(key)?; + SymmetricCryptoKey::try_from(key.as_mut_slice()) + }) + .transpose() + } + + fn get_decrypted_subtitle(&self, key: &SymmetricCryptoKey) -> Result { Ok(match self.r#type { CipherType::Login => { let Some(login) = &self.login else { return Ok(String::new()); }; - login.username.decrypt(enc, org_id)?.unwrap_or_default() + login.username.decrypt_with_key(key)?.unwrap_or_default() } CipherType::SecureNote => String::new(), CipherType::Card => { @@ -212,11 +252,12 @@ impl Cipher { let mut sub_title = String::new(); if let Some(brand) = &card.brand { - sub_title.push_str(&brand.decrypt(enc, org_id)?); + let brand: String = brand.decrypt_with_key(key)?; + sub_title.push_str(&brand); } if let Some(number) = &card.number { - let number = number.decrypt(enc, org_id)?; + let number: String = number.decrypt_with_key(key)?; let number_len = number.len(); if number_len > 4 { if !sub_title.is_empty() { @@ -242,14 +283,16 @@ impl Cipher { let mut sub_title = String::new(); if let Some(first_name) = &identity.first_name { - sub_title.push_str(&first_name.decrypt(enc, org_id)?); + let first_name: String = first_name.decrypt_with_key(key)?; + sub_title.push_str(&first_name); } if let Some(last_name) = &identity.last_name { if !sub_title.is_empty() { sub_title.push(' '); } - sub_title.push_str(&last_name.decrypt(enc, org_id)?); + let last_name: String = last_name.decrypt_with_key(key)?; + sub_title.push_str(&last_name); } sub_title @@ -258,16 +301,67 @@ impl Cipher { } } -impl Decryptable for Cipher { - fn decrypt(&self, enc: &EncryptionSettings, _: &Option) -> Result { - let org_id = &self.organization_id; +impl CipherView { + pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<()> { + let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; + let key = ciphers_key.as_ref().unwrap_or(key); + + let new_key = SymmetricCryptoKey::generate(rand::thread_rng()); + + self.key = Some(new_key.to_vec().expose().encrypt_with_key(key)?); + Ok(()) + } + + pub fn generate_checksums(&mut self) { + if let Some(uris) = self.login.as_mut().and_then(|l| l.uris.as_mut()) { + for uri in uris { + uri.generate_checksum(); + } + } + } + + pub fn remove_invalid_checksums(&mut self) { + if let Some(uris) = self.login.as_mut().and_then(|l| l.uris.as_mut()) { + uris.retain(|u| u.is_checksum_valid()); + } + } + + pub fn move_to_organization( + &mut self, + enc: &dyn KeyContainer, + organization_id: Uuid, + ) -> Result<()> { + // If the cipher has a key, we need to re-encrypt it with the new organization key + if let Some(cipher_key) = &mut self.key { + let old_key = enc + .get_key(&self.organization_id) + .ok_or(Error::VaultLocked)?; + + let new_key = enc + .get_key(&Some(organization_id)) + .ok_or(Error::VaultLocked)?; + + let dec_cipher_key = SensitiveVec::new(Box::new(cipher_key.decrypt_with_key(old_key)?)); + *cipher_key = dec_cipher_key.expose().encrypt_with_key(new_key)?; + } + + self.organization_id = Some(organization_id); + Ok(()) + } +} + +impl KeyDecryptable for Cipher { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; + let key = ciphers_key.as_ref().unwrap_or(key); + Ok(CipherListView { id: self.id, organization_id: self.organization_id, folder_id: self.folder_id, collection_ids: self.collection_ids.clone(), - name: self.name.decrypt(enc, org_id)?, - sub_title: self.get_decrypted_subtitle(enc, org_id)?, + 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, favorite: self.favorite, reprompt: self.reprompt, @@ -284,3 +378,211 @@ impl Decryptable for Cipher { }) } } + +impl LocateKey for Cipher { + fn locate_key<'a>( + &self, + enc: &'a dyn KeyContainer, + _: &Option, + ) -> Option<&'a SymmetricCryptoKey> { + enc.get_key(&self.organization_id) + } +} +impl LocateKey for CipherView { + fn locate_key<'a>( + &self, + enc: &'a dyn KeyContainer, + _: &Option, + ) -> Option<&'a SymmetricCryptoKey> { + enc.get_key(&self.organization_id) + } +} + +impl TryFrom for Cipher { + type Error = Error; + + fn try_from(cipher: CipherDetailsResponseModel) -> Result { + Ok(Self { + id: cipher.id, + organization_id: cipher.organization_id, + folder_id: cipher.folder_id, + collection_ids: cipher.collection_ids.unwrap_or_default(), + name: require!(EncString::try_from_optional(cipher.name)?), + notes: EncString::try_from_optional(cipher.notes)?, + r#type: require!(cipher.r#type).into(), + login: cipher.login.map(|l| (*l).try_into()).transpose()?, + identity: cipher.identity.map(|i| (*i).try_into()).transpose()?, + card: cipher.card.map(|c| (*c).try_into()).transpose()?, + secure_note: cipher.secure_note.map(|s| (*s).try_into()).transpose()?, + favorite: cipher.favorite.unwrap_or(false), + reprompt: cipher + .reprompt + .map(|r| r.into()) + .unwrap_or(CipherRepromptType::None), + organization_use_totp: cipher.organization_use_totp.unwrap_or(true), + edit: cipher.edit.unwrap_or(true), + view_password: cipher.view_password.unwrap_or(true), + local_data: None, // Not sent from server + attachments: cipher + .attachments + .map(|a| a.into_iter().map(|a| a.try_into()).collect()) + .transpose()?, + fields: cipher + .fields + .map(|f| f.into_iter().map(|f| f.try_into()).collect()) + .transpose()?, + password_history: cipher + .password_history + .map(|p| p.into_iter().map(|p| p.try_into()).collect()) + .transpose()?, + creation_date: require!(cipher.creation_date).parse()?, + deleted_date: cipher.deleted_date.map(|d| d.parse()).transpose()?, + revision_date: require!(cipher.revision_date).parse()?, + key: EncString::try_from_optional(cipher.key)?, + }) + } +} + +impl From for CipherType { + fn from(t: bitwarden_api_api::models::CipherType) -> Self { + match t { + bitwarden_api_api::models::CipherType::Login => CipherType::Login, + bitwarden_api_api::models::CipherType::SecureNote => CipherType::SecureNote, + bitwarden_api_api::models::CipherType::Card => CipherType::Card, + bitwarden_api_api::models::CipherType::Identity => CipherType::Identity, + } + } +} + +impl From for CipherRepromptType { + fn from(t: bitwarden_api_api::models::CipherRepromptType) -> Self { + match t { + bitwarden_api_api::models::CipherRepromptType::None => CipherRepromptType::None, + bitwarden_api_api::models::CipherRepromptType::Password => CipherRepromptType::Password, + } + } +} + +#[cfg(test)] +mod tests { + + use std::collections::HashMap; + + use super::*; + + fn generate_cipher() -> CipherView { + CipherView { + r#type: CipherType::Login, + login: Some(login::LoginView { + username: Some("test_username".to_string()), + password: Some("test_password".to_string()), + password_revision_date: None, + uris: None, + totp: None, + autofill_on_page_load: None, + fido2_credentials: None, + }), + id: "fd411a1a-fec8-4070-985d-0e6560860e69".parse().ok(), + organization_id: None, + folder_id: None, + collection_ids: vec![], + key: None, + name: "My test login".to_string(), + notes: None, + identity: None, + card: None, + secure_note: None, + favorite: false, + reprompt: CipherRepromptType::None, + organization_use_totp: true, + 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(), + } + } + + #[test] + fn test_generate_cipher_key() { + let key = SymmetricCryptoKey::generate(rand::thread_rng()); + + let original_cipher = generate_cipher(); + + // Check that the cipher gets encrypted correctly without it's own key + let cipher = generate_cipher(); + let no_key_cipher_enc = cipher.encrypt_with_key(&key).unwrap(); + let no_key_cipher_dec: CipherView = no_key_cipher_enc.decrypt_with_key(&key).unwrap(); + assert!(no_key_cipher_dec.key.is_none()); + assert_eq!(no_key_cipher_dec.name, original_cipher.name); + + let mut cipher = generate_cipher(); + cipher.generate_cipher_key(&key).unwrap(); + + // Check that the cipher gets encrypted correctly when it's assigned it's own key + let key_cipher_enc = cipher.encrypt_with_key(&key).unwrap(); + let key_cipher_dec: CipherView = key_cipher_enc.decrypt_with_key(&key).unwrap(); + assert!(key_cipher_dec.key.is_some()); + assert_eq!(key_cipher_dec.name, original_cipher.name); + } + + 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) + } + } + + #[test] + fn test_move_user_cipher_to_org() { + let org = uuid::Uuid::new_v4(); + + let enc = MockKeyContainer(HashMap::from([ + (None, SymmetricCryptoKey::generate(rand::thread_rng())), + (Some(org), SymmetricCryptoKey::generate(rand::thread_rng())), + ])); + + // Create a cipher with a user key + let mut cipher = generate_cipher(); + cipher + .generate_cipher_key(enc.get_key(&None).unwrap()) + .unwrap(); + + cipher.move_to_organization(&enc, org).unwrap(); + assert_eq!(cipher.organization_id, Some(org)); + + // Check that the cipher can be encrypted/decrypted with the new org key + let org_key = enc.get_key(&Some(org)).unwrap(); + let cipher_enc = cipher.encrypt_with_key(org_key).unwrap(); + let cipher_dec: CipherView = cipher_enc.decrypt_with_key(org_key).unwrap(); + + assert_eq!(cipher_dec.name, "My test login"); + } + + #[test] + fn test_move_user_cipher_to_org_manually() { + let org = uuid::Uuid::new_v4(); + + let enc = MockKeyContainer(HashMap::from([ + (None, SymmetricCryptoKey::generate(rand::thread_rng())), + (Some(org), SymmetricCryptoKey::generate(rand::thread_rng())), + ])); + + // Create a cipher with a user key + let mut cipher = generate_cipher(); + cipher + .generate_cipher_key(enc.get_key(&None).unwrap()) + .unwrap(); + + cipher.organization_id = Some(org); + + // Check that the cipher can not be encrypted, as the + // cipher key is tied to the user key and not the org key + let org_key = enc.get_key(&Some(org)).unwrap(); + assert!(cipher.encrypt_with_key(org_key).is_err()); + } +} diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden/src/vault/cipher/field.rs index 9fa05257a..1f18a9537 100644 --- a/crates/bitwarden/src/vault/cipher/field.rs +++ b/crates/bitwarden/src/vault/cipher/field.rs @@ -1,14 +1,13 @@ +use bitwarden_api_api::models::CipherFieldModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use uuid::Uuid; use super::linked_id::LinkedIdType; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, -}; +use crate::error::{require, Error, Result}; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -35,31 +34,58 @@ pub struct Field { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct FieldView { - name: Option, - value: Option, - r#type: FieldType, + pub(crate) name: Option, + pub(crate) value: Option, + pub(crate) r#type: FieldType, - linked_id: Option, + pub(crate) linked_id: Option, } -impl Encryptable for FieldView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyEncryptable for FieldView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Field { - name: self.name.encrypt(enc, org_id)?, - value: self.value.encrypt(enc, org_id)?, + name: self.name.encrypt_with_key(key)?, + value: self.value.encrypt_with_key(key)?, r#type: self.r#type, linked_id: self.linked_id, }) } } -impl Decryptable for Field { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for Field { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FieldView { - name: self.name.decrypt(enc, org_id)?, - value: self.value.decrypt(enc, org_id)?, + name: self.name.decrypt_with_key(key).ok().flatten(), + value: self.value.decrypt_with_key(key).ok().flatten(), r#type: self.r#type, linked_id: self.linked_id, }) } } + +impl TryFrom for Field { + type Error = Error; + + fn try_from(model: CipherFieldModel) -> Result { + Ok(Self { + name: EncString::try_from_optional(model.name)?, + value: EncString::try_from_optional(model.value)?, + r#type: require!(model.r#type).into(), + linked_id: model + .linked_id + .map(|id| (id as u32).try_into()) + .transpose()?, + }) + } +} + +impl From for FieldType { + fn from(model: bitwarden_api_api::models::FieldType) -> Self { + match model { + bitwarden_api_api::models::FieldType::Text => FieldType::Text, + bitwarden_api_api::models::FieldType::Hidden => FieldType::Hidden, + bitwarden_api_api::models::FieldType::Boolean => FieldType::Boolean, + bitwarden_api_api::models::FieldType::Linked => FieldType::Linked, + } + } +} diff --git a/crates/bitwarden/src/vault/cipher/identity.rs b/crates/bitwarden/src/vault/cipher/identity.rs index aace84152..f59166eec 100644 --- a/crates/bitwarden/src/vault/cipher/identity.rs +++ b/crates/bitwarden/src/vault/cipher/identity.rs @@ -1,12 +1,11 @@ +use bitwarden_api_api::models::CipherIdentityModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -56,52 +55,79 @@ pub struct IdentityView { pub license_number: Option, } -impl Encryptable for IdentityView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyEncryptable for IdentityView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Identity { - title: self.title.encrypt(enc, org_id)?, - first_name: self.first_name.encrypt(enc, org_id)?, - middle_name: self.middle_name.encrypt(enc, org_id)?, - last_name: self.last_name.encrypt(enc, org_id)?, - address1: self.address1.encrypt(enc, org_id)?, - address2: self.address2.encrypt(enc, org_id)?, - address3: self.address3.encrypt(enc, org_id)?, - city: self.city.encrypt(enc, org_id)?, - state: self.state.encrypt(enc, org_id)?, - postal_code: self.postal_code.encrypt(enc, org_id)?, - country: self.country.encrypt(enc, org_id)?, - company: self.company.encrypt(enc, org_id)?, - email: self.email.encrypt(enc, org_id)?, - phone: self.phone.encrypt(enc, org_id)?, - ssn: self.ssn.encrypt(enc, org_id)?, - username: self.username.encrypt(enc, org_id)?, - passport_number: self.passport_number.encrypt(enc, org_id)?, - license_number: self.license_number.encrypt(enc, org_id)?, + title: self.title.encrypt_with_key(key)?, + first_name: self.first_name.encrypt_with_key(key)?, + middle_name: self.middle_name.encrypt_with_key(key)?, + last_name: self.last_name.encrypt_with_key(key)?, + address1: self.address1.encrypt_with_key(key)?, + address2: self.address2.encrypt_with_key(key)?, + address3: self.address3.encrypt_with_key(key)?, + city: self.city.encrypt_with_key(key)?, + state: self.state.encrypt_with_key(key)?, + postal_code: self.postal_code.encrypt_with_key(key)?, + country: self.country.encrypt_with_key(key)?, + company: self.company.encrypt_with_key(key)?, + email: self.email.encrypt_with_key(key)?, + phone: self.phone.encrypt_with_key(key)?, + ssn: self.ssn.encrypt_with_key(key)?, + username: self.username.encrypt_with_key(key)?, + passport_number: self.passport_number.encrypt_with_key(key)?, + license_number: self.license_number.encrypt_with_key(key)?, }) } } -impl Decryptable for Identity { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for Identity { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(IdentityView { - title: self.title.decrypt(enc, org_id)?, - first_name: self.first_name.decrypt(enc, org_id)?, - middle_name: self.middle_name.decrypt(enc, org_id)?, - last_name: self.last_name.decrypt(enc, org_id)?, - address1: self.address1.decrypt(enc, org_id)?, - address2: self.address2.decrypt(enc, org_id)?, - address3: self.address3.decrypt(enc, org_id)?, - city: self.city.decrypt(enc, org_id)?, - state: self.state.decrypt(enc, org_id)?, - postal_code: self.postal_code.decrypt(enc, org_id)?, - country: self.country.decrypt(enc, org_id)?, - company: self.company.decrypt(enc, org_id)?, - email: self.email.decrypt(enc, org_id)?, - phone: self.phone.decrypt(enc, org_id)?, - ssn: self.ssn.decrypt(enc, org_id)?, - username: self.username.decrypt(enc, org_id)?, - passport_number: self.passport_number.decrypt(enc, org_id)?, - license_number: self.license_number.decrypt(enc, org_id)?, + title: self.title.decrypt_with_key(key).ok().flatten(), + first_name: self.first_name.decrypt_with_key(key).ok().flatten(), + middle_name: self.middle_name.decrypt_with_key(key).ok().flatten(), + last_name: self.last_name.decrypt_with_key(key).ok().flatten(), + address1: self.address1.decrypt_with_key(key).ok().flatten(), + address2: self.address2.decrypt_with_key(key).ok().flatten(), + address3: self.address3.decrypt_with_key(key).ok().flatten(), + city: self.city.decrypt_with_key(key).ok().flatten(), + state: self.state.decrypt_with_key(key).ok().flatten(), + postal_code: self.postal_code.decrypt_with_key(key).ok().flatten(), + country: self.country.decrypt_with_key(key).ok().flatten(), + company: self.company.decrypt_with_key(key).ok().flatten(), + email: self.email.decrypt_with_key(key).ok().flatten(), + phone: self.phone.decrypt_with_key(key).ok().flatten(), + ssn: self.ssn.decrypt_with_key(key).ok().flatten(), + username: self.username.decrypt_with_key(key).ok().flatten(), + passport_number: self.passport_number.decrypt_with_key(key).ok().flatten(), + license_number: self.license_number.decrypt_with_key(key).ok().flatten(), + }) + } +} + +impl TryFrom for Identity { + type Error = Error; + + fn try_from(identity: CipherIdentityModel) -> Result { + Ok(Self { + title: EncString::try_from_optional(identity.title)?, + first_name: EncString::try_from_optional(identity.first_name)?, + middle_name: EncString::try_from_optional(identity.middle_name)?, + last_name: EncString::try_from_optional(identity.last_name)?, + address1: EncString::try_from_optional(identity.address1)?, + address2: EncString::try_from_optional(identity.address2)?, + address3: EncString::try_from_optional(identity.address3)?, + city: EncString::try_from_optional(identity.city)?, + state: EncString::try_from_optional(identity.state)?, + postal_code: EncString::try_from_optional(identity.postal_code)?, + country: EncString::try_from_optional(identity.country)?, + company: EncString::try_from_optional(identity.company)?, + email: EncString::try_from_optional(identity.email)?, + phone: EncString::try_from_optional(identity.phone)?, + ssn: EncString::try_from_optional(identity.ssn)?, + username: EncString::try_from_optional(identity.username)?, + passport_number: EncString::try_from_optional(identity.passport_number)?, + license_number: EncString::try_from_optional(identity.license_number)?, }) } } diff --git a/crates/bitwarden/src/vault/cipher/linked_id.rs b/crates/bitwarden/src/vault/cipher/linked_id.rs index 692d59b77..b52d756c6 100644 --- a/crates/bitwarden/src/vault/cipher/linked_id.rs +++ b/crates/bitwarden/src/vault/cipher/linked_id.rs @@ -10,6 +10,7 @@ pub enum LinkedIdType { Identity(IdentityLinkedIdType), } +use crate::error::{Error, Result}; #[cfg(feature = "mobile")] use crate::UniffiCustomTypeConverter; #[cfg(feature = "mobile")] @@ -24,7 +25,13 @@ impl UniffiCustomTypeConverter for LinkedIdType { } fn from_custom(obj: Self) -> Self::Builtin { - serde_json::to_value(obj) + obj.into() + } +} + +impl From for u32 { + fn from(v: LinkedIdType) -> Self { + serde_json::to_value(v) .expect("LinkedIdType should be serializable") .as_u64() .expect("Not a numeric enum value") as u32 @@ -73,6 +80,43 @@ pub enum IdentityLinkedIdType { FullName = 418, } +impl TryFrom for LinkedIdType { + type Error = Error; + + fn try_from(value: u32) -> Result { + match value { + 100 => Ok(LinkedIdType::Login(LoginLinkedIdType::Username)), + 101 => Ok(LinkedIdType::Login(LoginLinkedIdType::Password)), + 300 => Ok(LinkedIdType::Card(CardLinkedIdType::CardholderName)), + 301 => Ok(LinkedIdType::Card(CardLinkedIdType::ExpMonth)), + 302 => Ok(LinkedIdType::Card(CardLinkedIdType::ExpYear)), + 303 => Ok(LinkedIdType::Card(CardLinkedIdType::Code)), + 304 => Ok(LinkedIdType::Card(CardLinkedIdType::Brand)), + 305 => Ok(LinkedIdType::Card(CardLinkedIdType::Number)), + 400 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Title)), + 401 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::MiddleName)), + 402 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Address1)), + 403 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Address2)), + 404 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Address3)), + 405 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::City)), + 406 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::State)), + 407 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::PostalCode)), + 408 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Country)), + 409 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Company)), + 410 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Email)), + 411 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Phone)), + 412 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Ssn)), + 413 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::Username)), + 414 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::PassportNumber)), + 415 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::LicenseNumber)), + 416 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::FirstName)), + 417 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::LastName)), + 418 => Ok(LinkedIdType::Identity(IdentityLinkedIdType::FullName)), + _ => Err(Error::MissingFields("LinkedIdType")), + } + } +} + #[cfg(test)] mod tests { diff --git a/crates/bitwarden/src/vault/cipher/local_data.rs b/crates/bitwarden/src/vault/cipher/local_data.rs index 1811ffa8b..6a85512c2 100644 --- a/crates/bitwarden/src/vault/cipher/local_data.rs +++ b/crates/bitwarden/src/vault/cipher/local_data.rs @@ -1,12 +1,6 @@ +use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, Encryptable}, - error::Result, -}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -24,8 +18,8 @@ pub struct LocalDataView { last_launched: Option, } -impl Encryptable for LocalDataView { - fn encrypt(self, _enc: &EncryptionSettings, _org_id: &Option) -> Result { +impl KeyEncryptable for LocalDataView { + fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { Ok(LocalData { last_used_date: self.last_used_date, last_launched: self.last_launched, @@ -33,8 +27,8 @@ impl Encryptable for LocalDataView { } } -impl Decryptable for LocalData { - fn decrypt(&self, _enc: &EncryptionSettings, _org_id: &Option) -> Result { +impl KeyDecryptable for LocalData { + fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { Ok(LocalDataView { last_used_date: self.last_used_date, last_launched: self.last_launched, diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden/src/vault/cipher/login.rs index 941aed221..aed691d9a 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden/src/vault/cipher/login.rs @@ -1,14 +1,14 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_api_api::models::{CipherLoginModel, CipherLoginUriModel}; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, -}; +use crate::error::{require, Error, Result}; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -29,6 +29,7 @@ pub enum UriMatchType { pub struct LoginUri { pub uri: Option, pub r#match: Option, + pub uri_checksum: Option, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -37,6 +38,54 @@ pub struct LoginUri { pub struct LoginUriView { pub uri: Option, pub r#match: Option, + pub uri_checksum: Option, +} + +impl LoginUriView { + pub(crate) fn is_checksum_valid(&self) -> bool { + let Some(uri) = &self.uri else { + return false; + }; + let Some(cs) = &self.uri_checksum else { + return false; + }; + let Ok(cs) = STANDARD.decode(cs) else { + return false; + }; + + use sha2::Digest; + let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize(); + + uri_hash.as_slice() == cs + } + + pub(crate) fn generate_checksum(&mut self) { + if let Some(uri) = &self.uri { + use sha2::Digest; + let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize(); + let uri_hash = STANDARD.encode(uri_hash.as_slice()); + self.uri_checksum = Some(uri_hash); + } + } +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct Fido2Credential { + pub credential_id: EncString, + pub key_type: EncString, + pub key_algorithm: EncString, + pub key_curve: EncString, + pub key_value: EncString, + pub rp_id: EncString, + pub user_handle: Option, + pub user_name: Option, + pub counter: EncString, + pub rp_name: Option, + pub user_display_name: Option, + pub discoverable: EncString, + pub creation_date: DateTime, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -50,6 +99,8 @@ pub struct Login { pub uris: Option>, pub totp: Option, pub autofill_on_page_load: Option, + + pub fido2_credentials: Option>, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -63,48 +114,180 @@ pub struct LoginView { pub uris: Option>, pub totp: Option, pub autofill_on_page_load: Option, + + // TODO: Remove this once the SDK supports state + pub fido2_credentials: Option>, } -impl Encryptable for LoginUriView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyEncryptable for LoginUriView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(LoginUri { - uri: self.uri.encrypt(enc, org_id)?, + uri: self.uri.encrypt_with_key(key)?, r#match: self.r#match, + uri_checksum: self.uri_checksum.encrypt_with_key(key)?, }) } } -impl Encryptable for LoginView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyEncryptable for LoginView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Login { - username: self.username.encrypt(enc, org_id)?, - password: self.password.encrypt(enc, org_id)?, + username: self.username.encrypt_with_key(key)?, + password: self.password.encrypt_with_key(key)?, password_revision_date: self.password_revision_date, - uris: self.uris.encrypt(enc, org_id)?, - totp: self.totp.encrypt(enc, org_id)?, + 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, }) } } -impl Decryptable for LoginUri { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for LoginUri { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(LoginUriView { - uri: self.uri.decrypt(enc, org_id)?, + uri: self.uri.decrypt_with_key(key)?, r#match: self.r#match, + uri_checksum: self.uri_checksum.decrypt_with_key(key)?, }) } } -impl Decryptable for Login { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for Login { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(LoginView { - username: self.username.decrypt(enc, org_id)?, - password: self.password.decrypt(enc, org_id)?, + username: self.username.decrypt_with_key(key).ok().flatten(), + password: self.password.decrypt_with_key(key).ok().flatten(), password_revision_date: self.password_revision_date, - uris: self.uris.decrypt(enc, org_id)?, - totp: self.totp.decrypt(enc, org_id)?, + 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.clone(), + }) + } +} + +impl TryFrom for Login { + type Error = Error; + + fn try_from(login: CipherLoginModel) -> Result { + Ok(Self { + username: EncString::try_from_optional(login.username)?, + password: EncString::try_from_optional(login.password)?, + password_revision_date: login + .password_revision_date + .map(|d| d.parse()) + .transpose()?, + uris: login + .uris + .map(|v| v.into_iter().map(|u| u.try_into()).collect()) + .transpose()?, + totp: EncString::try_from_optional(login.totp)?, + autofill_on_page_load: login.autofill_on_page_load, + fido2_credentials: login + .fido2_credentials + .map(|v| v.into_iter().map(|c| c.try_into()).collect()) + .transpose()?, + }) + } +} + +impl TryFrom for LoginUri { + type Error = Error; + + fn try_from(uri: CipherLoginUriModel) -> Result { + Ok(Self { + uri: EncString::try_from_optional(uri.uri)?, + r#match: uri.r#match.map(|m| m.into()), + uri_checksum: EncString::try_from_optional(uri.uri_checksum)?, + }) + } +} + +impl From for UriMatchType { + fn from(value: bitwarden_api_api::models::UriMatchType) -> Self { + match value { + bitwarden_api_api::models::UriMatchType::Domain => Self::Domain, + bitwarden_api_api::models::UriMatchType::Host => Self::Host, + bitwarden_api_api::models::UriMatchType::StartsWith => Self::StartsWith, + bitwarden_api_api::models::UriMatchType::Exact => Self::Exact, + bitwarden_api_api::models::UriMatchType::RegularExpression => Self::RegularExpression, + bitwarden_api_api::models::UriMatchType::Never => Self::Never, + } + } +} + +impl TryFrom for Fido2Credential { + type Error = Error; + + 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()?, + key_algorithm: require!(value.key_algorithm).parse()?, + key_curve: require!(value.key_curve).parse()?, + key_value: require!(value.key_value).parse()?, + rp_id: require!(value.rp_id).parse()?, + user_handle: EncString::try_from_optional(value.user_handle) + .ok() + .flatten(), + user_name: EncString::try_from_optional(value.user_name).ok().flatten(), + counter: require!(value.counter).parse()?, + rp_name: EncString::try_from_optional(value.rp_name).ok().flatten(), + user_display_name: EncString::try_from_optional(value.user_display_name) + .ok() + .flatten(), + discoverable: require!(value.discoverable).parse()?, + creation_date: value.creation_date.parse()?, }) } } + +#[cfg(test)] +mod tests { + #[test] + fn test_valid_checksum() { + let uri = super::LoginUriView { + uri: Some("https://example.com".to_string()), + r#match: Some(super::UriMatchType::Domain), + uri_checksum: Some("EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk=".to_string()), + }; + assert!(uri.is_checksum_valid()); + } + + #[test] + fn test_invalid_checksum() { + let uri = super::LoginUriView { + uri: Some("https://example.com".to_string()), + r#match: Some(super::UriMatchType::Domain), + uri_checksum: Some("UtSgIv8LYfEdOu7yqjF7qXWhmouYGYC8RSr7/ryZg5Q=".to_string()), + }; + assert!(!uri.is_checksum_valid()); + } + + #[test] + fn test_missing_checksum() { + let uri = super::LoginUriView { + uri: Some("https://example.com".to_string()), + r#match: Some(super::UriMatchType::Domain), + uri_checksum: None, + }; + assert!(!uri.is_checksum_valid()); + } + + #[test] + fn test_generate_checksum() { + let mut uri = super::LoginUriView { + uri: Some("https://test.com".to_string()), + r#match: Some(super::UriMatchType::Domain), + uri_checksum: None, + }; + + uri.generate_checksum(); + + assert_eq!( + uri.uri_checksum.unwrap().as_str(), + "OWk2vQvwYD1nhLZdA+ltrpBWbDa2JmHyjUEWxRZSS8w=" + ); + } +} diff --git a/crates/bitwarden/src/vault/cipher/mod.rs b/crates/bitwarden/src/vault/cipher/mod.rs index c891f439d..c2b49eb37 100644 --- a/crates/bitwarden/src/vault/cipher/mod.rs +++ b/crates/bitwarden/src/vault/cipher/mod.rs @@ -9,4 +9,9 @@ pub(crate) mod local_data; pub(crate) mod login; pub(crate) mod secure_note; -pub use cipher::{Cipher, CipherListView, CipherView}; +pub use attachment::{ + Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView, +}; +pub use cipher::{Cipher, CipherListView, CipherRepromptType, CipherType, CipherView}; +pub use field::FieldView; +pub use secure_note::SecureNoteType; diff --git a/crates/bitwarden/src/vault/cipher/secure_note.rs b/crates/bitwarden/src/vault/cipher/secure_note.rs index 422a55da1..ebcbc3fa4 100644 --- a/crates/bitwarden/src/vault/cipher/secure_note.rs +++ b/crates/bitwarden/src/vault/cipher/secure_note.rs @@ -1,13 +1,10 @@ +use bitwarden_api_api::models::CipherSecureNoteModel; +use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, Encryptable}, - error::Result, -}; +use crate::error::{require, Error, Result}; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -27,21 +24,39 @@ pub struct SecureNote { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SecureNoteView { - r#type: SecureNoteType, + pub(crate) r#type: SecureNoteType, } -impl Encryptable for SecureNoteView { - fn encrypt(self, _enc: &EncryptionSettings, _org_id: &Option) -> Result { +impl KeyEncryptable for SecureNoteView { + fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { Ok(SecureNote { r#type: self.r#type, }) } } -impl Decryptable for SecureNote { - fn decrypt(&self, _enc: &EncryptionSettings, _org_id: &Option) -> Result { +impl KeyDecryptable for SecureNote { + fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { Ok(SecureNoteView { r#type: self.r#type, }) } } + +impl TryFrom for SecureNote { + type Error = Error; + + fn try_from(model: CipherSecureNoteModel) -> Result { + Ok(Self { + r#type: require!(model.r#type).into(), + }) + } +} + +impl From for SecureNoteType { + fn from(model: bitwarden_api_api::models::SecureNoteType) -> Self { + match model { + bitwarden_api_api::models::SecureNoteType::Generic => SecureNoteType::Generic, + } + } +} diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden/src/vault/collection.rs index 38863a946..ce881cb98 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden/src/vault/collection.rs @@ -1,18 +1,18 @@ +use bitwarden_api_api::models::CollectionDetailsResponseModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyContainer, KeyDecryptable, LocateKey, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString}, - error::Result, -}; +use crate::error::{require, Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct Collection { - id: Uuid, + id: Option, organization_id: Uuid, name: EncString, @@ -26,7 +26,7 @@ pub struct Collection { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct CollectionView { - id: Uuid, + id: Option, organization_id: Uuid, name: String, @@ -36,15 +36,22 @@ pub struct CollectionView { read_only: bool, } -impl Decryptable for Collection { - fn decrypt(&self, enc: &EncryptionSettings, _: &Option) -> Result { - let org_id = Some(self.organization_id); - +impl LocateKey for Collection { + fn locate_key<'a>( + &self, + enc: &'a dyn KeyContainer, + _: &Option, + ) -> Option<&'a SymmetricCryptoKey> { + enc.get_key(&Some(self.organization_id)) + } +} +impl KeyDecryptable for Collection { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(CollectionView { id: self.id, organization_id: self.organization_id, - name: self.name.decrypt(enc, &org_id)?, + name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), external_id: self.external_id.clone(), hide_passwords: self.hide_passwords, @@ -52,3 +59,18 @@ impl Decryptable for Collection { }) } } + +impl TryFrom for Collection { + type Error = Error; + + fn try_from(collection: CollectionDetailsResponseModel) -> Result { + Ok(Collection { + id: collection.id, + organization_id: require!(collection.organization_id), + name: require!(collection.name).parse()?, + external_id: collection.external_id, + hide_passwords: collection.hide_passwords.unwrap_or(false), + read_only: collection.read_only.unwrap_or(false), + }) + } +} diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs index 97d861310..65b18e54b 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden/src/vault/folder.rs @@ -1,19 +1,19 @@ +use bitwarden_api_api::models::FolderResponseModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, -}; +use crate::error::{require, Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct Folder { - id: Uuid, + id: Option, name: EncString, revision_date: DateTime, } @@ -22,27 +22,41 @@ pub struct Folder { #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct FolderView { - id: Uuid, - name: String, - revision_date: DateTime, + pub id: Option, + pub name: String, + pub revision_date: DateTime, } -impl Encryptable for FolderView { - fn encrypt(self, enc: &EncryptionSettings, _org: &Option) -> Result { +impl LocateKey for FolderView {} +impl KeyEncryptable for FolderView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Folder { id: self.id, - name: self.name.encrypt(enc, &None)?, + name: self.name.encrypt_with_key(key)?, revision_date: self.revision_date, }) } } -impl Decryptable for Folder { - fn decrypt(&self, enc: &EncryptionSettings, _org: &Option) -> Result { +impl LocateKey for Folder {} +impl KeyDecryptable for Folder { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FolderView { id: self.id, - name: self.name.decrypt(enc, &None)?, + name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), revision_date: self.revision_date, }) } } + +impl TryFrom for Folder { + type Error = Error; + + fn try_from(folder: FolderResponseModel) -> Result { + Ok(Folder { + id: folder.id, + name: require!(EncString::try_from_optional(folder.name)?), + revision_date: require!(folder.revision_date).parse()?, + }) + } +} diff --git a/crates/bitwarden/src/vault/mod.rs b/crates/bitwarden/src/vault/mod.rs index 9eecd6620..2addfec6b 100644 --- a/crates/bitwarden/src/vault/mod.rs +++ b/crates/bitwarden/src/vault/mod.rs @@ -3,9 +3,15 @@ mod collection; mod folder; mod password_history; mod send; +#[cfg(feature = "mobile")] +mod totp; -pub use cipher::{Cipher, CipherListView, CipherView}; +pub use cipher::*; pub use collection::{Collection, CollectionView}; pub use folder::{Folder, FolderView}; pub use password_history::{PasswordHistory, PasswordHistoryView}; pub use send::{Send, SendListView, SendView}; +#[cfg(feature = "mobile")] +pub(crate) use totp::generate_totp; +#[cfg(feature = "mobile")] +pub use totp::TotpResponse; diff --git a/crates/bitwarden/src/vault/password_history.rs b/crates/bitwarden/src/vault/password_history.rs index 8566c9870..2ec20116b 100644 --- a/crates/bitwarden/src/vault/password_history.rs +++ b/crates/bitwarden/src/vault/password_history.rs @@ -1,13 +1,12 @@ +use bitwarden_api_api::models::CipherPasswordHistoryModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString, Encryptable}, - error::Result, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -25,24 +24,36 @@ pub struct PasswordHistoryView { last_used_date: DateTime, } -impl Encryptable for PasswordHistoryView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl LocateKey for PasswordHistoryView {} +impl KeyEncryptable for PasswordHistoryView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(PasswordHistory { - password: self.password.encrypt(enc, org_id)?, + password: self.password.encrypt_with_key(key)?, last_used_date: self.last_used_date, }) } } -impl Decryptable for PasswordHistory { - fn decrypt( +impl LocateKey for PasswordHistory {} +impl KeyDecryptable for PasswordHistory { + fn decrypt_with_key( &self, - enc: &EncryptionSettings, - org_id: &Option, - ) -> Result { + key: &SymmetricCryptoKey, + ) -> Result { Ok(PasswordHistoryView { - password: self.password.decrypt(enc, org_id)?, + password: self.password.decrypt_with_key(key).ok().unwrap_or_default(), last_used_date: self.last_used_date, }) } } + +impl TryFrom for PasswordHistory { + type Error = Error; + + fn try_from(model: CipherPasswordHistoryModel) -> Result { + Ok(Self { + password: model.password.parse()?, + last_used_date: model.last_used_date.parse()?, + }) + } +} diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 124b9dc2f..37e6efcc9 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -1,35 +1,42 @@ +use base64::{ + engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}, + Engine, +}; +use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; +use bitwarden_crypto::{ + derive_shareable_key, generate_random_bytes, CryptoError, EncString, KeyDecryptable, + KeyEncryptable, LocateKey, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{derive_shareable_key, Decryptable, EncString, Encryptable, SymmetricCryptoKey}, - error::Result, -}; +use crate::error::{require, Error, Result}; + +const SEND_ITERATIONS: u32 = 100_000; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendFile { - pub id: String, + pub id: Option, pub file_name: EncString, - pub size: String, + pub size: Option, /// Readable size, ex: "4.2 KB" or "1.43 GB" - pub size_name: String, + pub size_name: Option, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendFileView { - pub id: String, + pub id: Option, pub file_name: String, - pub size: String, + pub size: Option, /// Readable size, ex: "4.2 KB" or "1.43 GB" - pub size_name: String, + pub size_name: Option, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -40,7 +47,7 @@ pub struct SendText { pub hidden: bool, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendTextView { @@ -48,7 +55,7 @@ pub struct SendTextView { pub hidden: bool, } -#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] +#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema, PartialEq)] #[repr(u8)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] pub enum SendType { @@ -60,8 +67,8 @@ pub enum SendType { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct Send { - pub id: Uuid, - pub access_id: String, + pub id: Option, + pub access_id: Option, pub name: EncString, pub notes: Option, @@ -82,17 +89,24 @@ pub struct Send { pub expiration_date: Option>, } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendView { - pub id: Uuid, - pub access_id: String, + pub id: Option, + pub access_id: Option, pub name: String, pub notes: Option, - pub key: EncString, - pub password: Option, + /// Base64 encoded key + pub key: Option, + /// Replace or add a password to an existing send. The SDK will always return None when + /// decrypting a [Send] + /// TODO: We should revisit this, one variant is to have `[Create, Update]SendView` DTOs. + pub new_password: Option, + /// Denote if an existing send has a password. The SDK will ignore this value when creating or + /// updating sends. + pub has_password: bool, pub r#type: SendType, pub file: Option, @@ -112,8 +126,8 @@ pub struct SendView { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct SendListView { - pub id: Uuid, - pub access_id: String, + pub id: Option, + pub access_id: Option, pub name: String, @@ -126,86 +140,82 @@ pub struct SendListView { } impl Send { - fn get_key( - key: &EncString, - enc: &EncryptionSettings, - org_id: &Option, - ) -> Result { - let key: Vec = enc.decrypt_bytes(key, org_id)?; - let key = derive_shareable_key(key.try_into().unwrap(), "send", Some("send")); - Ok(key) + pub(crate) fn get_key( + send_key: &EncString, + enc_key: &SymmetricCryptoKey, + ) -> Result { + let key: Vec = send_key.decrypt_with_key(enc_key)?; + Self::derive_shareable_key(&key) } - pub(crate) fn get_encryption( - key: &EncString, - enc: &EncryptionSettings, - org_id: &Option, - ) -> Result { - let key = Send::get_key(key, enc, org_id)?; - Ok(EncryptionSettings::new_single_key(key)) + fn derive_shareable_key(key: &[u8]) -> Result { + let key = key.try_into().map_err(|_| CryptoError::InvalidKeyLen)?; + Ok(derive_shareable_key(key, "send", Some("send"))) } } -impl Decryptable for SendText { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for SendText { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(SendTextView { - text: self.text.decrypt(enc, org_id)?, + text: self.text.decrypt_with_key(key)?, hidden: self.hidden, }) } } -impl Encryptable for SendTextView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyEncryptable for SendTextView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(SendText { - text: self.text.encrypt(enc, org_id)?, + text: self.text.encrypt_with_key(key)?, hidden: self.hidden, }) } } -impl Decryptable for SendFile { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyDecryptable for SendFile { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(SendFileView { id: self.id.clone(), - file_name: self.file_name.decrypt(enc, org_id)?, + file_name: self.file_name.decrypt_with_key(key)?, size: self.size.clone(), size_name: self.size_name.clone(), }) } } -impl Encryptable for SendFileView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { +impl KeyEncryptable for SendFileView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(SendFile { id: self.id.clone(), - file_name: self.file_name.encrypt(enc, org_id)?, + file_name: self.file_name.encrypt_with_key(key)?, size: self.size.clone(), size_name: self.size_name.clone(), }) } } -impl Decryptable for Send { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { - // For sends, we first decrypt the send key with the user key, and stretch it to it's full size - let enc_owned = Send::get_encryption(&self.key, enc, org_id)?; - - // For the rest of the fields, we ignore the provided EncryptionSettings and use a new one with the stretched key - let enc = &enc_owned; +impl LocateKey for Send {} +impl KeyDecryptable for Send { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + // For sends, we first decrypt the send key with the user key, and stretch it to it's full + // size For the rest of the fields, we ignore the provided SymmetricCryptoKey and + // the stretched key + let k: Vec = self.key.decrypt_with_key(key)?; + let key = Send::derive_shareable_key(&k)?; Ok(SendView { id: self.id, access_id: self.access_id.clone(), - name: self.name.decrypt(enc, org_id)?, - notes: self.notes.decrypt(enc, org_id)?, - key: self.key.clone(), - password: self.password.clone(), + name: self.name.decrypt_with_key(&key).ok().unwrap_or_default(), + notes: self.notes.decrypt_with_key(&key).ok().flatten(), + key: Some(URL_SAFE_NO_PAD.encode(k)), + new_password: None, + has_password: self.password.is_some(), r#type: self.r#type, - file: self.file.decrypt(enc, org_id)?, - text: self.text.decrypt(enc, org_id)?, + file: self.file.decrypt_with_key(&key).ok().flatten(), + text: self.text.decrypt_with_key(&key).ok().flatten(), max_access_count: self.max_access_count, access_count: self.access_count, @@ -219,19 +229,18 @@ impl Decryptable for Send { } } -impl Decryptable for Send { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { - // For sends, we first decrypt the send key with the user key, and stretch it to it's full size - let enc_owned = Send::get_encryption(&self.key, enc, org_id)?; - - // For the rest of the fields, we ignore the provided EncryptionSettings and use a new one with the stretched key - let enc = &enc_owned; +impl KeyDecryptable for Send { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + // For sends, we first decrypt the send key with the user key, and stretch it to it's full + // size For the rest of the fields, we ignore the provided SymmetricCryptoKey and + // the stretched key + let key = Send::get_key(&self.key, key)?; Ok(SendListView { id: self.id, access_id: self.access_id.clone(), - name: self.name.decrypt(enc, org_id)?, + name: self.name.decrypt_with_key(&key)?, r#type: self.r#type, disabled: self.disabled, @@ -243,27 +252,42 @@ impl Decryptable for Send { } } -impl Encryptable for SendView { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { - // For sends, we first decrypt the send key with the user key, and stretch it to it's full size - let key = Send::get_key(&self.key, enc, org_id)?; - let enc_owned = EncryptionSettings::new_single_key(key); - - // For the rest of the fields, we ignore the provided EncryptionSettings and use a new one with the stretched key - let enc = &enc_owned; +impl LocateKey for SendView {} +impl KeyEncryptable for SendView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + // For sends, we first decrypt the send key with the user key, and stretch it to it's full + // size For the rest of the fields, we ignore the provided SymmetricCryptoKey and + // the stretched key + let k = match (self.key, self.id) { + // Existing send, decrypt key + (Some(k), _) => URL_SAFE_NO_PAD + .decode(k) + .map_err(|_| CryptoError::InvalidKey)?, + // New send, generate random key + (None, None) => { + let key: [u8; 16] = generate_random_bytes(); + key.to_vec() + } + // Existing send without key + _ => return Err(CryptoError::InvalidKey), + }; + let send_key = Send::derive_shareable_key(&k)?; Ok(Send { id: self.id, access_id: self.access_id, - name: self.name.encrypt(enc, org_id)?, - notes: self.notes.encrypt(enc, org_id)?, - key: self.key.clone(), - password: self.password.clone(), + name: self.name.encrypt_with_key(&send_key)?, + notes: self.notes.encrypt_with_key(&send_key)?, + key: k.encrypt_with_key(key)?, + password: self.new_password.map(|password| { + let password = bitwarden_crypto::pbkdf2(password.as_bytes(), &k, SEND_ITERATIONS); + STANDARD.encode(password) + }), r#type: self.r#type, - file: self.file.encrypt(enc, org_id)?, - text: self.text.encrypt(enc, org_id)?, + file: self.file.encrypt_with_key(&send_key)?, + text: self.text.encrypt_with_key(&send_key)?, max_access_count: self.max_access_count, access_count: self.access_count, @@ -277,10 +301,73 @@ impl Encryptable for SendView { } } +impl TryFrom for Send { + type Error = Error; + + fn try_from(send: SendResponseModel) -> Result { + Ok(Send { + id: send.id, + access_id: send.access_id, + name: require!(send.name).parse()?, + notes: EncString::try_from_optional(send.notes)?, + key: require!(send.key).parse()?, + password: send.password, + r#type: require!(send.r#type).into(), + file: send.file.map(|f| (*f).try_into()).transpose()?, + text: send.text.map(|t| (*t).try_into()).transpose()?, + max_access_count: send.max_access_count.map(|s| s as u32), + access_count: require!(send.access_count) as u32, + disabled: send.disabled.unwrap_or(false), + hide_email: send.hide_email.unwrap_or(false), + revision_date: require!(send.revision_date).parse()?, + deletion_date: require!(send.deletion_date).parse()?, + expiration_date: send.expiration_date.map(|s| s.parse()).transpose()?, + }) + } +} + +impl From for SendType { + fn from(t: bitwarden_api_api::models::SendType) -> Self { + match t { + bitwarden_api_api::models::SendType::Text => SendType::Text, + bitwarden_api_api::models::SendType::File => SendType::File, + } + } +} + +impl TryFrom for SendFile { + type Error = Error; + + fn try_from(file: SendFileModel) -> Result { + Ok(SendFile { + id: file.id, + file_name: require!(file.file_name).parse()?, + size: file.size.map(|v| v.to_string()), + size_name: file.size_name, + }) + } +} + +impl TryFrom for SendText { + type Error = Error; + + fn try_from(text: SendTextModel) -> Result { + Ok(SendText { + text: EncString::try_from_optional(text.text)?, + hidden: text.hidden.unwrap_or(false), + }) + } +} + #[cfg(test)] mod tests { - use super::Send; - use crate::client::{encryption_settings::EncryptionSettings, kdf::Kdf, UserLoginMethod}; + use bitwarden_crypto::{KeyDecryptable, KeyEncryptable}; + + use super::{Send, SendText, SendTextView, SendType}; + use crate::{ + client::{encryption_settings::EncryptionSettings, Kdf, UserLoginMethod}, + vault::SendView, + }; #[test] fn test_get_send_key() { @@ -293,45 +380,210 @@ mod tests { iterations: 345123.try_into().unwrap(), }, }, - "asdfasdfasdf".into(), + "asdfasdfasdf", "2.majkL1/hNz9yptLqNAUSnw==|RiOzMTTJMG948qu8O3Zm1EQUO2E8BuTwFKnO9LWQjMzxMWJM5GbyOq2/A+tumPbTERt4JWur/FKfgHb+gXuYiEYlXPMuVBvT7nv4LPytJuM=|IVqMxHJeR1ZXY0sGngTC0x+WqbG8p6V+BTrdgBbQXjM=".parse().unwrap(), "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap(), ).unwrap(); - // Create a send object, the only value we really care about here is the key + let k = enc.get_key(&None).unwrap(); + + let send_key = "2.+1KUfOX8A83Xkwk1bumo/w==|Nczvv+DTkeP466cP/wMDnGK6W9zEIg5iHLhcuQG6s+M=|SZGsfuIAIaGZ7/kzygaVUau3LeOvJUlolENBOU+LX7g=" + .parse() + .unwrap(); + + // Get the send key + let send_key = Send::get_key(&send_key, k).unwrap(); + let send_key_b64 = send_key.to_base64(); + assert_eq!(send_key_b64.expose(), "IR9ImHGm6rRuIjiN7csj94bcZR5WYTJj5GtNfx33zm6tJCHUl+QZlpNPba8g2yn70KnOHsAODLcR0um6E3MAlg=="); + } + + fn build_encryption_settings() -> EncryptionSettings { + EncryptionSettings::new( + &UserLoginMethod::Username { + client_id: "test".into(), + email: "test@bitwarden.com".into(), + kdf: Kdf::PBKDF2 { + iterations: 600_000.try_into().unwrap(), + }, + }, + "asdfasdfasdf", + "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(), + "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap(), + ).unwrap() + } + + #[test] + pub fn test_decrypt() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + let send = Send { - id: "d7fb1e7f-9053-43c0-a02c-b0690098685a".parse().unwrap(), - access_id: "fx7711OQwEOgLLBpAJhoWg".into(), - name: "2.u5vXPAepUZ+4lI2vGGKiGg==|hEouC4SvCCb/ifzZzLcfSw==|E2VZUVffehczfIuRSlX2vnPRfflBtXef5tzsWvRrtfM=" - .parse() + id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + r#type: SendType::Text, + name: "2.STIyTrfDZN/JXNDN9zNEMw==|NDLum8BHZpPNYhJo9ggSkg==|UCsCLlBO3QzdPwvMAWs2VVwuE6xwOx/vxOooPObqnEw=".parse() .unwrap(), notes: None, - key: "2.+1KUfOX8A83Xkwk1bumo/w==|Nczvv+DTkeP466cP/wMDnGK6W9zEIg5iHLhcuQG6s+M=|SZGsfuIAIaGZ7/kzygaVUau3LeOvJUlolENBOU+LX7g=" - .parse() - .unwrap(), + file: None, + text: Some(SendText { + text: "2.2VPyLzk1tMLug0X3x7RkaQ==|mrMt9vbZsCJhJIj4eebKyg==|aZ7JeyndytEMR1+uEBupEvaZuUE69D/ejhfdJL8oKq0=".parse().ok(), + hidden: false, + }), + key: "2.KLv/j0V4Ebs0dwyPdtt4vw==|jcrFuNYN1Qb3onBlwvtxUV/KpdnR1LPRL4EsCoXNAt4=|gHSywGy4Rj/RsCIZFwze4s2AACYKBtqDXTrQXjkgtIE=".parse().unwrap(), + max_access_count: None, + access_count: 0, password: None, - r#type: super::SendType::File, - file: Some(super::SendFile { - id: "7f129hzwu0umkmnmsghkt486w96p749c".into(), - file_name: "2.pnszM3slsCVlOIzuWrfCpA==|85zCg+X8GODvIAPf1Yt3K75YP+ub3wVAi1UvwOVXhPgUo9Gsu23FJgYSOkyKu3Vr|OvTrOugwRH7Mp2BWSuPlfxovoWt9oDRdi1Qo3xHUcdQ=" - .parse() - .unwrap(), - size: "1251825".into(), - size_name: "1.19 MB".into(), + disabled: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + expiration_date: None, + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + hide_email: false, + }; + + let view: SendView = send.decrypt_with_key(key).unwrap(); + + let expected = SendView { + id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_string(), + notes: None, + key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + new_password: None, + has_password: false, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, }), - text: None, max_access_count: None, access_count: 0, disabled: false, hide_email: false, - revision_date: "2023-08-25T09:14:53Z".parse().unwrap(), - deletion_date: "2023-09-25T09:14:53Z".parse().unwrap(), + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), expiration_date: None, }; - // Get the send key - let send_key = Send::get_key(&send.key, &enc, &None).unwrap(); - let send_key_b64 = send_key.to_base64(); - assert_eq!(send_key_b64, "IR9ImHGm6rRuIjiN7csj94bcZR5WYTJj5GtNfx33zm6tJCHUl+QZlpNPba8g2yn70KnOHsAODLcR0um6E3MAlg=="); + assert_eq!(view, expected); + } + + #[test] + pub fn test_encrypt() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + + let view = SendView { + id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(), + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_string(), + notes: None, + key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + new_password: None, + has_password: false, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, + }), + max_access_count: None, + access_count: 0, + disabled: false, + hide_email: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + expiration_date: None, + }; + + // Re-encrypt and decrypt again to ensure encrypt works + let v: SendView = view + .clone() + .encrypt_with_key(key) + .unwrap() + .decrypt_with_key(key) + .unwrap(); + assert_eq!(v, view); + } + + #[test] + pub fn test_create() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + + let view = SendView { + id: None, + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_string(), + notes: None, + key: None, + new_password: None, + has_password: false, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, + }), + max_access_count: None, + access_count: 0, + disabled: false, + hide_email: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + expiration_date: None, + }; + + // Re-encrypt and decrypt again to ensure encrypt works + let v: SendView = view + .clone() + .encrypt_with_key(key) + .unwrap() + .decrypt_with_key(key) + .unwrap(); + + // Ignore key when comparing + let t = SendView { key: None, ..v }; + assert_eq!(t, view); + } + + #[test] + pub fn test_create_password() { + let enc = build_encryption_settings(); + let key = enc.get_key(&None).unwrap(); + + let view = SendView { + id: None, + access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()), + name: "Test".to_owned(), + notes: None, + key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()), + new_password: Some("abc123".to_owned()), + has_password: false, + r#type: SendType::Text, + file: None, + text: Some(SendTextView { + text: Some("This is a test".to_owned()), + hidden: false, + }), + max_access_count: None, + access_count: 0, + disabled: false, + hide_email: false, + revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(), + deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(), + expiration_date: None, + }; + + let send: Send = view.encrypt_with_key(key).unwrap(); + + assert_eq!( + send.password, + Some("vTIDfdj3FTDbejmMf+mJWpYdMXsxfeSd1Sma3sjCtiQ=".to_owned()) + ); + + let v: SendView = send.decrypt_with_key(key).unwrap(); + assert_eq!(v.new_password, None); + assert!(v.has_password); } } diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden/src/vault/totp.rs new file mode 100644 index 000000000..4586eda48 --- /dev/null +++ b/crates/bitwarden/src/vault/totp.rs @@ -0,0 +1,305 @@ +use std::{collections::HashMap, str::FromStr}; + +use chrono::{DateTime, Utc}; +use hmac::{Hmac, Mac}; +use reqwest::Url; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::error::{Error, Result}; + +type HmacSha1 = Hmac; +type HmacSha256 = Hmac; +type HmacSha512 = Hmac; + +const BASE32_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +const STEAM_CHARS: &str = "23456789BCDFGHJKMNPQRTVWXY"; + +const DEFAULT_ALGORITHM: Algorithm = Algorithm::Sha1; +const DEFAULT_DIGITS: u32 = 6; +const DEFAULT_PERIOD: u32 = 30; + +#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct TotpResponse { + /// Generated TOTP code + pub code: String, + /// Time period + pub period: u32, +} + +/// Generate a OATH or RFC 6238 TOTP code from a provided key. +/// +/// +/// +/// Key can be either: +/// - A base32 encoded string +/// - OTP Auth URI +/// - Steam URI +/// +/// Supports providing an optional time, and defaults to current system time if none is provided. +/// +/// Arguments: +/// - `key` - The key to generate the TOTP code from +/// - `time` - The time in UTC to generate the TOTP code for, defaults to current system time +pub(crate) fn generate_totp(key: String, time: Option>) -> Result { + let params: Totp = key.parse()?; + + let time = time.unwrap_or_else(Utc::now); + + let otp = params.derive_otp(time.timestamp()); + + Ok(TotpResponse { + code: otp, + period: params.period, + }) +} + +#[derive(Clone, Copy, Debug)] +enum Algorithm { + Sha1, + Sha256, + Sha512, + Steam, +} + +impl Algorithm { + // Derive the HMAC hash for the given algorithm + fn derive_hash(&self, key: &[u8], time: &[u8]) -> Vec { + fn compute_digest(digest: D, time: &[u8]) -> Vec { + digest.chain_update(time).finalize().into_bytes().to_vec() + } + + match self { + Algorithm::Sha1 => compute_digest( + HmacSha1::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + Algorithm::Sha256 => compute_digest( + HmacSha256::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + Algorithm::Sha512 => compute_digest( + HmacSha512::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + Algorithm::Steam => compute_digest( + HmacSha1::new_from_slice(key).expect("hmac new_from_slice should not fail"), + time, + ), + } + } +} + +#[derive(Debug)] +struct Totp { + algorithm: Algorithm, + digits: u32, + period: u32, + secret: Vec, +} + +impl Totp { + fn derive_otp(&self, time: i64) -> String { + let time = time / self.period as i64; + + let hash = self + .algorithm + .derive_hash(&self.secret, time.to_be_bytes().as_ref()); + let binary = derive_binary(hash); + + if let Algorithm::Steam = self.algorithm { + derive_steam_otp(binary, self.digits) + } else { + let otp = binary % 10_u32.pow(self.digits); + format!("{1:00$}", self.digits as usize, otp) + } + } +} + +impl FromStr for Totp { + type Err = Error; + + /// Parses the provided key and returns the corresponding `Totp`. + /// + /// Key can be either: + /// - A base32 encoded string + /// - OTP Auth URI + /// - Steam URI + fn from_str(key: &str) -> Result { + let params = if key.starts_with("otpauth://") { + let url = Url::parse(key).map_err(|_| "Unable to parse URL")?; + let parts: HashMap<_, _> = url.query_pairs().collect(); + + Totp { + algorithm: parts + .get("algorithm") + .and_then(|v| match v.to_uppercase().as_ref() { + "SHA1" => Some(Algorithm::Sha1), + "SHA256" => Some(Algorithm::Sha256), + "SHA512" => Some(Algorithm::Sha512), + _ => None, + }) + .unwrap_or(DEFAULT_ALGORITHM), + digits: parts + .get("digits") + .and_then(|v| v.parse().ok()) + .map(|v: u32| v.clamp(0, 10)) + .unwrap_or(DEFAULT_DIGITS), + period: parts + .get("period") + .and_then(|v| v.parse().ok()) + .map(|v: u32| v.max(1)) + .unwrap_or(DEFAULT_PERIOD), + secret: decode_b32( + &parts + .get("secret") + .map(|v| v.to_string()) + .ok_or("Missing secret in otpauth URI")?, + ), + } + } else if let Some(secret) = key.strip_prefix("steam://") { + Totp { + algorithm: Algorithm::Steam, + digits: 5, + period: DEFAULT_PERIOD, + secret: decode_b32(secret), + } + } else { + Totp { + algorithm: DEFAULT_ALGORITHM, + digits: DEFAULT_DIGITS, + period: DEFAULT_PERIOD, + secret: decode_b32(key), + } + }; + + Ok(params) + } +} + +/// Derive the Steam OTP from the hash with the given number of digits. +fn derive_steam_otp(binary: u32, digits: u32) -> String { + let mut full_code = binary & 0x7fffffff; + + (0..digits) + .map(|_| { + let index = full_code as usize % STEAM_CHARS.len(); + let char = STEAM_CHARS + .chars() + .nth(index) + .expect("Should always be within range"); + full_code /= STEAM_CHARS.len() as u32; + char + }) + .collect() +} + +/// Derive the OTP from the hash with the given number of digits. +fn derive_binary(hash: Vec) -> u32 { + let offset = (hash.last().unwrap_or(&0) & 15) as usize; + + ((hash[offset] & 127) as u32) << 24 + | (hash[offset + 1] as u32) << 16 + | (hash[offset + 2] as u32) << 8 + | hash[offset + 3] as u32 +} + +/// This code is migrated from our javascript implementation and is not technically a correct base32 +/// decoder since we filter out various characters, and use exact chunking. +fn decode_b32(s: &str) -> Vec { + let s = s.to_uppercase(); + + let mut bits = String::new(); + for c in s.chars() { + if let Some(i) = BASE32_CHARS.find(c) { + bits.push_str(&format!("{:05b}", i)); + } + } + let mut bytes = Vec::new(); + + for chunk in bits.as_bytes().chunks_exact(8) { + let byte_str = std::str::from_utf8(chunk).expect("The value is a valid string"); + let byte = u8::from_str_radix(byte_str, 2).expect("The value is a valid binary string"); + bytes.push(byte); + } + + bytes +} + +#[cfg(test)] +mod tests { + use chrono::Utc; + + use super::*; + + #[test] + fn test_decode_b32() { + let res = decode_b32("WQIQ25BRKZYCJVYP"); + assert_eq!(res, vec![180, 17, 13, 116, 49, 86, 112, 36, 215, 15]); + + let res = decode_b32("ABCD123"); + assert_eq!(res, vec![0, 68, 61]); + } + + #[test] + fn test_generate_totp() { + let cases = vec![ + ("WQIQ25BRKZYCJVYP", "194506"), // valid base32 + ("wqiq25brkzycjvyp", "194506"), // lowercase + ("PIUDISEQYA", "829846"), // non padded + ("PIUDISEQYA======", "829846"), // padded + ("PIUD1IS!EQYA=", "829846"), // sanitized + // Steam + ("steam://HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", "7W6CJ"), + ("steam://ABCD123", "N26DF"), + // Various weird lengths + ("ddfdf", "932653"), + ("HJSGFJHDFDJDJKSDFD", "000034"), + ("xvdsfasdfasdasdghsgsdfg", "403786"), + ("KAKFJWOSFJ12NWL", "093430"), + ]; + + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + + for (key, expected_code) in cases { + let response = generate_totp(key.to_string(), time).unwrap(); + + assert_eq!(response.code, expected_code, "wrong code for key: {key}"); + assert_eq!(response.period, 30); + } + } + + #[test] + fn test_generate_otpauth() { + let key = "otpauth://totp/test-account?secret=WQIQ25BRKZYCJVYP".to_string(); + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + let response = generate_totp(key, time).unwrap(); + + assert_eq!(response.code, "194506".to_string()); + assert_eq!(response.period, 30); + } + + #[test] + fn test_generate_otpauth_period() { + let key = "otpauth://totp/test-account?secret=WQIQ25BRKZYCJVYP&period=60".to_string(); + let time = Some( + DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") + .unwrap() + .with_timezone(&Utc), + ); + let response = generate_totp(key, time).unwrap(); + + assert_eq!(response.code, "730364".to_string()); + assert_eq!(response.period, 60); + } +} diff --git a/crates/bitwarden/tests/register.rs b/crates/bitwarden/tests/register.rs new file mode 100644 index 000000000..8e523e26f --- /dev/null +++ b/crates/bitwarden/tests/register.rs @@ -0,0 +1,41 @@ +/// Integration test for registering a new user and unlocking the vault +#[cfg(feature = "mobile")] +#[tokio::test] +async fn test_register_initialize_crypto() { + use std::num::NonZeroU32; + + use bitwarden::{ + mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest}, + Client, + }; + use bitwarden_crypto::Kdf; + + let mut client = Client::new(None); + + let email = "test@bitwarden.com"; + let password = "test123"; + let kdf = Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }; + + let register_response = client + .auth() + .make_register_keys(email.to_owned(), password.to_owned(), kdf.clone()) + .unwrap(); + + // Ensure we can initialize the crypto with the new keys + client + .crypto() + .initialize_user_crypto(InitUserCryptoRequest { + kdf_params: kdf, + email: email.to_owned(), + private_key: register_response.keys.private.to_string(), + + method: InitUserCryptoMethod::Password { + password: password.to_owned(), + user_key: register_response.encrypted_user_key, + }, + }) + .await + .unwrap(); +} diff --git a/crates/bitwarden/uniffi.toml b/crates/bitwarden/uniffi.toml index 5c66e5382..7a804ef1d 100644 --- a/crates/bitwarden/uniffi.toml +++ b/crates/bitwarden/uniffi.toml @@ -1,6 +1,9 @@ [bindings.kotlin] package_name = "com.bitwarden.core" +generate_immutable_records = true +android = true [bindings.swift] ffi_module_name = "BitwardenCoreFFI" module_name = "BitwardenCore" +generate_immutable_records = true diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 29252d133..3fa5efd4a 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -1,32 +1,30 @@ [package] name = "bw" version = "0.0.2" -edition = "2021" -rust-version = "1.60" -authors = ["Bitwarden Inc"] -license-file = "LICENSE" -repository = "https://github.com/bitwarden/sdk" -homepage = "https://bitwarden.com" description = """ Bitwarden Password Manager CLI """ keywords = ["bitwarden", "password-manager", "cli"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true [dependencies] -clap = { version = "4.3.0", features = ["derive", "env"] } -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -log = "0.4.18" -env_logger = "0.10.0" +bitwarden = { workspace = true, features = ["internal", "mobile"] } +bitwarden-cli = { workspace = true } +clap = { version = "4.5.1", features = ["derive", "env"] } color-eyre = "0.6" +env_logger = "0.11.1" inquire = "0.6.2" - -bitwarden = { path = "../bitwarden", version = "0.3.0", features = [ - "internal", - "mobile", -] } -bitwarden-cli = { path = "../bitwarden-cli", version = "0.1.0" } +log = "0.4.20" +tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } [dev-dependencies] -tempfile = "3.5.0" +tempfile = "3.10.0" + +[lints] +workspace = true diff --git a/crates/bw/LICENSE b/crates/bw/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bw/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bw/src/auth/login.rs b/crates/bw/src/auth/login.rs index 1c169817f..91e740a3a 100644 --- a/crates/bw/src/auth/login.rs +++ b/crates/bw/src/auth/login.rs @@ -3,6 +3,7 @@ use bitwarden::{ ApiKeyLoginRequest, PasswordLoginRequest, TwoFactorEmailRequest, TwoFactorProvider, TwoFactorRequest, }, + platform::SyncRequest, Client, }; use bitwarden_cli::text_prompt_when_none; @@ -10,15 +11,16 @@ use color_eyre::eyre::{bail, Result}; use inquire::{Password, Text}; use log::{debug, error, info}; -pub(crate) async fn password_login(mut client: Client, email: Option) -> Result<()> { +pub(crate) async fn login_password(mut client: Client, email: Option) -> Result<()> { let email = text_prompt_when_none("Email", email)?; let password = Password::new("Password").without_confirmation().prompt()?; - let kdf = client.prelogin(email.clone()).await?; + let kdf = client.auth().prelogin(email.clone()).await?; let result = client - .password_login(&PasswordLoginRequest { + .auth() + .login_password(&PasswordLoginRequest { email: email.clone(), password: password.clone(), two_factor: None, @@ -45,6 +47,7 @@ pub(crate) async fn password_login(mut client: Client, email: Option) -> } else if let Some(tf) = two_factor.email { // Send token client + .auth() .send_two_factor_email(&TwoFactorEmailRequest { email: email.clone(), password: password.clone(), @@ -64,7 +67,8 @@ pub(crate) async fn password_login(mut client: Client, email: Option) -> }; let result = client - .password_login(&PasswordLoginRequest { + .auth() + .login_password(&PasswordLoginRequest { email, password, two_factor, @@ -77,10 +81,17 @@ pub(crate) async fn password_login(mut client: Client, email: Option) -> debug!("{:?}", result); } + let res = client + .sync(&SyncRequest { + exclude_subdomains: Some(true), + }) + .await?; + info!("{:#?}", res); + Ok(()) } -pub(crate) async fn api_key_login( +pub(crate) async fn login_api_key( mut client: Client, client_id: Option, client_secret: Option, @@ -91,7 +102,8 @@ pub(crate) async fn api_key_login( let password = Password::new("Password").without_confirmation().prompt()?; let result = client - .api_key_login(&ApiKeyLoginRequest { + .auth() + .login_api_key(&ApiKeyLoginRequest { client_id, client_secret, password, @@ -102,3 +114,22 @@ pub(crate) async fn api_key_login( Ok(()) } + +pub(crate) async fn login_device( + mut client: Client, + email: Option, + device_identifier: Option, +) -> Result<()> { + let email = text_prompt_when_none("Email", email)?; + let device_identifier = text_prompt_when_none("Device Identifier", device_identifier)?; + + let auth = client.auth().login_device(email, device_identifier).await?; + + println!("Fingerprint: {}", auth.fingerprint); + + Text::new("Press enter once approved").prompt()?; + + client.auth().login_device_complete(auth).await?; + + Ok(()) +} diff --git a/crates/bw/src/auth/mod.rs b/crates/bw/src/auth/mod.rs index a745a70f0..1f165f5f3 100644 --- a/crates/bw/src/auth/mod.rs +++ b/crates/bw/src/auth/mod.rs @@ -1,2 +1,2 @@ mod login; -pub(crate) use login::{api_key_login, password_login}; +pub(crate) use login::{login_api_key, login_device, login_password}; diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index 73e64a4aa..6674bda1e 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -1,5 +1,7 @@ use bitwarden::{ - auth::RegisterRequest, client::client_settings::ClientSettings, tool::PasswordGeneratorRequest, + auth::RegisterRequest, + client::client_settings::ClientSettings, + generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest}, }; use bitwarden_cli::{install_color_eyre, text_prompt_when_none, Color}; use clap::{command, Args, CommandFactory, Parser, Subcommand}; @@ -76,6 +78,11 @@ enum LoginCommands { client_id: Option, client_secret: Option, }, + Device { + #[arg(short = 'e', long, help = "Email address")] + email: Option, + device_identifier: Option, + }, } #[derive(Subcommand, Clone)] @@ -87,7 +94,7 @@ enum ItemCommands { #[derive(Subcommand, Clone)] enum GeneratorCommands { Password(PasswordGeneratorArgs), - Passphrase {}, + Passphrase(PassphraseGeneratorArgs), } #[derive(Args, Clone)] @@ -113,6 +120,18 @@ struct PasswordGeneratorArgs { length: u8, } +#[derive(Args, Clone)] +struct PassphraseGeneratorArgs { + #[arg(long, default_value = "3", help = "Number of words in the passphrase")] + words: u8, + #[arg(long, default_value = " ", help = "Separator between words")] + separator: char, + #[arg(long, action, help = "Capitalize the first letter of each word")] + capitalize: bool, + #[arg(long, action, help = "Include a number in one of the words")] + include_number: bool, +} + #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); @@ -143,12 +162,18 @@ async fn process_commands() -> Result<()> { match args.command { // FIXME: Rust CLI will not support password login! LoginCommands::Password { email } => { - auth::password_login(client, email).await?; + auth::login_password(client, email).await?; } LoginCommands::ApiKey { client_id, client_secret, - } => auth::api_key_login(client, client_id, client_secret).await?, + } => auth::login_api_key(client, client_id, client_secret).await?, + LoginCommands::Device { + email, + device_identifier, + } => { + auth::login_device(client, email, device_identifier).await?; + } } return Ok(()); } @@ -199,14 +224,26 @@ async fn process_commands() -> Result<()> { uppercase: args.uppercase, numbers: args.numbers, special: args.special, - length: Some(args.length), + length: args.length, ..Default::default() }) .await?; println!("{}", password); } - GeneratorCommands::Passphrase {} => todo!(), + GeneratorCommands::Passphrase(args) => { + let passphrase = client + .generator() + .passphrase(PassphraseGeneratorRequest { + num_words: args.words, + word_separator: args.separator.to_string(), + capitalize: args.capitalize, + include_number: args.include_number, + }) + .await?; + + println!("{}", passphrase); + } }, }; diff --git a/crates/bws/CHANGELOG.md b/crates/bws/CHANGELOG.md index 15fcfdf3b..642266c70 100644 --- a/crates/bws/CHANGELOG.md +++ b/crates/bws/CHANGELOG.md @@ -9,6 +9,31 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added +- Add a `BWS_CONFIG_FILE` environment variable to specify the location of the config file (#571) +- The `bws` CLI is now available as a Docker image (`docker run -it bitwarden/bws --help`) (#305) +- The `bws` CLI releases are now code signed on Windows and Mac (#534, #535) + +### Fixed + +- Re-add output options to the help menu after they were accidentally removed (#477) + +### Changed + +- Switched TLS backend to `rusttls`, removing the dependency on `OpenSSL` (#374) +- Updated MSRV for `bws` to `1.71.0` (#589) + +## [0.4.0] - 2023-12-21 + +### Added + +- Ability to output secrets in an `env` format with `bws` (#320) +- Basic state to avoid reauthenticating every run, used when setting the `state_file_dir` key in the + config (#388) + +## [0.3.1] - 2023-10-13 + +### Added + - Support for shell autocompletion with the `bws completions` command (#103) - When running `bws` with no args, the help text is now printed to `stderr` instead of `stdout` to be consistent with `bws subcommand` behavior (#190) diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index ed4dc3b91..d27daca6f 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -1,47 +1,51 @@ [package] name = "bws" -version = "0.3.0" -edition = "2021" -rust-version = "1.60" -authors = ["Bitwarden Inc"] -license-file = "LICENSE" -repository = "https://github.com/bitwarden/sdk" -homepage = "https://bitwarden.com" +version = "0.4.0" description = """ Bitwarden Secrets Manager CLI """ keywords = ["bitwarden", "secrets-manager", "cli"] +exclude = ["Dockerfile*", "entitlements.plist"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true [dependencies] -bat = { version = "0.23.0", features = [ +bat = { version = "0.24.0", features = [ "regex-onig", ], default-features = false } -chrono = { version = "0.4.26", features = [ +bitwarden = { workspace = true, features = ["secrets"] } +bitwarden-cli = { workspace = true } +chrono = { version = "0.4.35", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.3.0", features = ["derive", "env", "string"] } -clap_complete = "4.3.2" +clap = { version = "4.5.1", features = ["derive", "env", "string"] } +clap_complete = "4.5.0" color-eyre = "0.6" -comfy-table = "^7.0.1" +comfy-table = "^7.1.0" directories = "5.0.1" -env_logger = "0.10.0" -log = "0.4.18" -serde = "^1.0.163" -serde_json = "^1.0.96" +env_logger = "0.11.1" +log = "0.4.20" +regex = { version = "1.10.3", features = [ + "std", + "perf", +], default-features = false } +serde = "^1.0.196" +serde_json = "^1.0.113" serde_yaml = "0.9" -supports-color = "2.0.0" -thiserror = "1.0.40" -tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros"] } -toml = "0.8.0" -uuid = { version = "^1.3.3", features = ["serde"] } - -bitwarden = { path = "../bitwarden", version = "0.3.0", features = ["secrets"] } +supports-color = "3.0.0" +thiserror = "1.0.57" +tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] } +toml = "0.8.10" +uuid = { version = "^1.7.0", features = ["serde"] } [dev-dependencies] -tempfile = "3.5.0" +tempfile = "3.10.0" -[target.'cfg(target_os = "linux")'.dependencies] -openssl = { version = "0.10", features = ["vendored"] } +[lints] +workspace = true diff --git a/crates/bws/Cross.toml b/crates/bws/Cross.toml deleted file mode 100644 index 79b22e7f8..000000000 --- a/crates/bws/Cross.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Install OpenSSL -[target.aarch64-unknown-linux-gnu] -pre-build = [ - "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH", -] diff --git a/crates/bws/Dockerfile b/crates/bws/Dockerfile new file mode 100644 index 000000000..cc9e1c481 --- /dev/null +++ b/crates/bws/Dockerfile @@ -0,0 +1,49 @@ +############################################### +# Build stage # +############################################### +FROM --platform=$BUILDPLATFORM rust:1.76 AS build + +# Docker buildx supplies the value for this arg +ARG TARGETPLATFORM + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Copy required project files +COPY . /app + +# Build project +WORKDIR /app/crates/bws +RUN cargo build --release --bin bws + +# Bundle bws dependencies +RUN mkdir /lib-bws +RUN ldd /app/target/release/bws | tr -s '[:blank:]' '\n' | grep '^/' | xargs -I % cp % /lib-bws + +# Make a HOME directory for the app stage +RUN mkdir -p /home/app + +############################################### +# App stage # +############################################### +FROM scratch + +ARG TARGETPLATFORM +LABEL com.bitwarden.product="bitwarden" + +# Set a HOME directory +COPY --from=build /home/app /home/app +ENV HOME=/home/app + +# Copy built project from the build stage +WORKDIR /usr/local/bin +COPY --from=build /app/target/release/bws . + +# Copy certs +COPY --from=build /etc/ssl/certs /etc/ssl/certs + +# Copy bws dependencies +COPY --from=build /lib-bws /lib + +ENTRYPOINT ["bws"] diff --git a/crates/bws/Dockerfile.dockerignore b/crates/bws/Dockerfile.dockerignore new file mode 100644 index 000000000..50f4b1230 --- /dev/null +++ b/crates/bws/Dockerfile.dockerignore @@ -0,0 +1,4 @@ +* +!crates/* +!Cargo.toml +!Cargo.lock diff --git a/crates/bws/LICENSE b/crates/bws/LICENSE deleted file mode 100644 index e9d496ff7..000000000 --- a/crates/bws/LICENSE +++ /dev/null @@ -1,295 +0,0 @@ -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/crates/bws/README.md b/crates/bws/README.md index 11ea23814..cb9c268fb 100644 --- a/crates/bws/README.md +++ b/crates/bws/README.md @@ -44,3 +44,21 @@ echo 'source <(/path/to/bws completions bash)' >> ~/.bashrc For more detailed documentation, please refer to the [Secrets Manager CLI help article](https://bitwarden.com/help/secrets-manager-cli/). + +## Docker + +We also provide a docker image preloaded with the `bws` cli. + +```bash +# From the root of the repository +docker build -f crates/bws/Dockerfile -t bitwarden/bws . + +docker run --rm -it bitwarden/bws --help +``` + +To use a configuration file, utilize docker +[bind mounting](https://docs.docker.com/storage/bind-mounts/) to expose it to the container: + +```bash +docker run --rm -it -v "$HOME"/.bws:/home/app/.bws bitwarden/bws --help +``` diff --git a/crates/bws/entitlements.plist b/crates/bws/entitlements.plist new file mode 100644 index 000000000..aeaa15049 --- /dev/null +++ b/crates/bws/entitlements.plist @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + \ No newline at end of file diff --git a/crates/bws/src/config.rs b/crates/bws/src/config.rs index ecad2f6a5..6cc471f97 100644 --- a/crates/bws/src/config.rs +++ b/crates/bws/src/config.rs @@ -19,6 +19,7 @@ pub(crate) struct Profile { pub server_base: Option, pub server_api: Option, pub server_identity: Option, + pub state_file_dir: Option, } // TODO: This could probably be derived with a macro if we start adding more fields @@ -28,6 +29,7 @@ pub(crate) enum ProfileKey { server_base, server_api, server_identity, + state_file_dir, } impl ProfileKey { @@ -36,6 +38,7 @@ impl ProfileKey { ProfileKey::server_base => p.server_base = Some(value), ProfileKey::server_api => p.server_api = Some(value), ProfileKey::server_identity => p.server_identity = Some(value), + ProfileKey::state_file_dir => p.state_file_dir = Some(value), } } } @@ -43,23 +46,28 @@ impl ProfileKey { pub(crate) const FILENAME: &str = "config"; pub(crate) const DIRECTORY: &str = ".bws"; -fn get_config_path(config_file: Option<&Path>, ensure_folder_exists: bool) -> PathBuf { - let config_file = config_file.map(ToOwned::to_owned).unwrap_or_else(|| { - let base_dirs = BaseDirs::new().unwrap(); - base_dirs.home_dir().join(DIRECTORY).join(FILENAME) - }); +fn get_config_path(config_file: Option<&Path>, ensure_folder_exists: bool) -> Result { + let config_file = match config_file { + Some(path) => path.to_owned(), + None => { + let Some(base_dirs) = BaseDirs::new() else { + bail!("A valid home directory doesn't exist"); + }; + base_dirs.home_dir().join(DIRECTORY).join(FILENAME) + } + }; if ensure_folder_exists { if let Some(parent_folder) = config_file.parent() { - std::fs::create_dir_all(parent_folder).unwrap(); + std::fs::create_dir_all(parent_folder)?; } } - config_file + Ok(config_file) } pub(crate) fn load_config(config_file: Option<&Path>, must_exist: bool) -> Result { - let file = get_config_path(config_file, false); + let file = get_config_path(config_file, false)?; let content = match file.exists() { true => read_to_string(file), @@ -72,7 +80,7 @@ pub(crate) fn load_config(config_file: Option<&Path>, must_exist: bool) -> Resul } fn write_config(config: Config, config_file: Option<&Path>) -> Result<()> { - let file = get_config_path(config_file, true); + let file = get_config_path(config_file, true)?; let content = toml::to_string_pretty(&config)?; @@ -118,6 +126,7 @@ impl Profile { server_base: Some(url.to_string()), server_api: None, server_identity: None, + state_file_dir: None, }) } pub(crate) fn api_url(&self) -> Result { diff --git a/crates/bws/src/main.rs b/crates/bws/src/main.rs index 5e6da19d8..d57aee5a6 100644 --- a/crates/bws/src/main.rs +++ b/crates/bws/src/main.rs @@ -1,8 +1,8 @@ use std::{path::PathBuf, process, str::FromStr}; use bitwarden::{ - auth::login::AccessTokenLoginRequest, - client::{client_settings::ClientSettings, AccessToken}, + auth::{login::AccessTokenLoginRequest, AccessToken}, + client::client_settings::ClientSettings, secrets_manager::{ projects::{ ProjectCreateRequest, ProjectGetRequest, ProjectPutRequest, ProjectsDeleteRequest, @@ -14,6 +14,7 @@ use bitwarden::{ }, }, }; +use bitwarden_cli::{install_color_eyre, Color}; use clap::{ArgGroup, CommandFactory, Parser, Subcommand}; use clap_complete::Shell; use color_eyre::eyre::{bail, Result}; @@ -21,9 +22,10 @@ use log::error; mod config; mod render; +mod state; use config::ProfileKey; -use render::{serialize_response, Color, Output}; +use render::{serialize_response, Output}; use uuid::Uuid; #[derive(Parser, Debug)] @@ -33,19 +35,20 @@ struct Cli { #[command(subcommand)] command: Option, - #[arg(short = 'o', long, global = true, value_enum, default_value_t = Output::JSON, help="Output format", hide = true)] + #[arg(short = 'o', long, global = true, value_enum, default_value_t = Output::JSON, help="Output format")] output: Output, #[arg(short = 'c', long, global = true, value_enum, default_value_t = Color::Auto, help="Use colors in the output")] color: Color, - #[arg(short = 't', long, global = true, env = ACCESS_TOKEN_KEY_VAR_NAME, hide_env_values = true, help="Specify access token for the service account")] + #[arg(short = 't', long, global = true, env = ACCESS_TOKEN_KEY_VAR_NAME, hide_env_values = true, help="Specify access token for the machine account")] access_token: Option, #[arg( short = 'f', long, global = true, + env = CONFIG_FILE_KEY_VAR_NAME, help = format!("[default: ~/{}/{}] Config file to use", config::DIRECTORY, config::FILENAME) )] config_file: Option, @@ -227,27 +230,21 @@ async fn main() -> Result<()> { } const ACCESS_TOKEN_KEY_VAR_NAME: &str = "BWS_ACCESS_TOKEN"; +const CONFIG_FILE_KEY_VAR_NAME: &str = "BWS_CONFIG_FILE"; const PROFILE_KEY_VAR_NAME: &str = "BWS_PROFILE"; const SERVER_URL_KEY_VAR_NAME: &str = "BWS_SERVER_URL"; #[allow(clippy::comparison_chain)] async fn process_commands() -> Result<()> { let cli = Cli::parse(); + let color = cli.color; - let color = cli.color.is_enabled(); - if color { - color_eyre::install()?; - } else { - // Use an empty theme to disable error coloring - color_eyre::config::HookBuilder::new() - .theme(color_eyre::config::Theme::new()) - .install()?; - } + install_color_eyre(color)?; let Some(command) = cli.command else { let mut cmd = Cli::command(); eprintln!("{}", cmd.render_help().ansi()); - return Ok(()); + std::process::exit(1); }; // These commands don't require authentication, so we process them first @@ -272,7 +269,7 @@ async fn process_commands() -> Result<()> { profile } else if let Some(access_token) = cli.access_token { AccessToken::from_str(&access_token)? - .service_account_id + .access_token_id .to_string() } else { String::from("default") @@ -302,6 +299,7 @@ async fn process_commands() -> Result<()> { Some(key) => key, None => bail!("Missing access token"), }; + let access_token_obj: AccessToken = access_token.parse()?; let profile = get_config_profile( &cli.server_url, @@ -311,6 +309,7 @@ async fn process_commands() -> Result<()> { )?; let settings = profile + .clone() .map(|p| -> Result<_> { Ok(ClientSettings { identity_url: p.identity_url()?, @@ -320,11 +319,20 @@ async fn process_commands() -> Result<()> { }) .transpose()?; + let state_file_path = state::get_state_file_path( + profile.and_then(|p| p.state_file_dir).map(Into::into), + access_token_obj.access_token_id.to_string(), + )?; + let mut client = bitwarden::Client::new(settings); // Load session or return if no session exists let _ = client - .access_token_login(&AccessTokenLoginRequest { access_token }) + .auth() + .login_access_token(&AccessTokenLoginRequest { + access_token, + state_file: state_file_path, + }) .await?; let organization_id = match client.get_access_token_organization() { @@ -618,7 +626,7 @@ fn get_config_profile( profile.to_owned() } else { AccessToken::from_str(access_token)? - .service_account_id + .access_token_id .to_string() }; diff --git a/crates/bws/src/render.rs b/crates/bws/src/render.rs index 4f2a32e9a..7acdd30a6 100644 --- a/crates/bws/src/render.rs +++ b/crates/bws/src/render.rs @@ -1,4 +1,5 @@ use bitwarden::secrets_manager::{projects::ProjectResponse, secrets::SecretResponse}; +use bitwarden_cli::Color; use chrono::{DateTime, Utc}; use clap::ValueEnum; use comfy_table::Table; @@ -9,46 +10,58 @@ use serde::Serialize; pub(crate) enum Output { JSON, YAML, + Env, Table, TSV, None, } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] -pub(crate) enum Color { - No, - Yes, - Auto, -} - -impl Color { - pub(crate) fn is_enabled(self) -> bool { - match self { - Color::No => false, - Color::Yes => true, - Color::Auto => supports_color::on(supports_color::Stream::Stdout).is_some(), - } - } -} - const ASCII_HEADER_ONLY: &str = " -- "; pub(crate) fn serialize_response, const N: usize>( data: T, output: Output, - color: bool, + color: Color, ) { match output { Output::JSON => { - let mut text = serde_json::to_string_pretty(&data).unwrap(); - // Yaml/table/tsv serializations add a newline at the end, so we do the same here for consistency + 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); } Output::YAML => { - let text = serde_yaml::to_string(&data).unwrap(); + let text = serde_yaml::to_string(&data).expect("Serialize should be infallible"); pretty_print("yaml", &text, 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]) { + format!("{}=\"{}\"", row[1], row[2]) + } else { + commented_out = true; + format!("# {}=\"{}\"", row[1], row[2].replace('\n', "\n# ")) + } + }) + .collect(); + + if commented_out { + text.push(String::from( + "\n# one or more secrets have been commented-out due to a problematic key name", + )); + } + + pretty_print("sh", &format!("{}\n", text.join("\n")), color); + } Output::Table => { let mut table = Table::new(); table @@ -72,19 +85,20 @@ pub(crate) fn serialize_response, const N: usiz } } -fn pretty_print(language: &str, data: &str, color: bool) { - if color { +fn pretty_print(language: &str, data: &str, color: Color) { + if color.is_enabled() { bat::PrettyPrinter::new() .input_from_bytes(data.as_bytes()) .language(language) .print() - .unwrap(); + .expect("Input is valid"); } else { print!("{}", data); } } -// We're using const generics for the array lengths to make sure the header count and value count match +// We're using const generics for the array lengths to make sure the header count and value count +// match pub(crate) trait TableSerialize: Sized { fn get_headers() -> [&'static str; N]; fn get_values(&self) -> Vec<[String; N]>; diff --git a/crates/bws/src/state.rs b/crates/bws/src/state.rs new file mode 100644 index 000000000..9c90da81f --- /dev/null +++ b/crates/bws/src/state.rs @@ -0,0 +1,20 @@ +use std::path::PathBuf; + +use color_eyre::eyre::Result; + +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); + + if let Some(parent_folder) = state_file_path.parent() { + std::fs::create_dir_all(parent_folder)?; + } + + return Ok(Some(state_file_path)); + } + + Ok(None) +} diff --git a/crates/memory-testing/.gitignore b/crates/memory-testing/.gitignore new file mode 100644 index 000000000..53752db25 --- /dev/null +++ b/crates/memory-testing/.gitignore @@ -0,0 +1 @@ +output diff --git a/crates/memory-testing/Cargo.toml b/crates/memory-testing/Cargo.toml new file mode 100644 index 000000000..120fcaad1 --- /dev/null +++ b/crates/memory-testing/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "memory-testing" +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 + +[dependencies] +bitwarden-crypto = { workspace = true } +comfy-table = "7.1.0" +hex = "0.4.3" +serde = "1.0.196" +serde_json = "1.0.113" +zeroize = "1.7.0" diff --git a/crates/memory-testing/Dockerfile b/crates/memory-testing/Dockerfile new file mode 100644 index 000000000..fdcf5de00 --- /dev/null +++ b/crates/memory-testing/Dockerfile @@ -0,0 +1,26 @@ +############################################### +# Build stage # +############################################### +FROM rust:1.76 AS build + +# Copy required project files +COPY . /app + +# Build project +WORKDIR /app +RUN cargo build -p memory-testing + +############################################### +# App stage # +############################################### +FROM debian:bookworm-slim + +# This specifically needs to run as root to be able to capture core dumps +USER root + +RUN apt-get update && apt-get install -y --no-install-recommends gdb=13.1-3 && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Copy built project from the build stage +COPY --from=build /app/target/debug/memory-testing /app/target/debug/capture-dumps /app/crates/memory-testing/cases.json ./ + +CMD [ "/capture-dumps", "./memory-testing", "/output" ] diff --git a/crates/memory-testing/Dockerfile.dockerignore b/crates/memory-testing/Dockerfile.dockerignore new file mode 100644 index 000000000..50f4b1230 --- /dev/null +++ b/crates/memory-testing/Dockerfile.dockerignore @@ -0,0 +1,4 @@ +* +!crates/* +!Cargo.toml +!Cargo.lock diff --git a/crates/memory-testing/cases.json b/crates/memory-testing/cases.json new file mode 100644 index 000000000..eb85e2aca --- /dev/null +++ b/crates/memory-testing/cases.json @@ -0,0 +1,9 @@ +{ + "symmetric_key": [ + { + "key": "FfhVVP8fmFIZY1WmRszPmRmVCxXNWVcJffPrbkywTPtBNkgfhYGT+D9sVGizYXrPffuj2yoyWqMwF9iF5aMQhQ==", + "decrypted_key_hex": "15f85554ff1f9852196355a646cccf9919950b15cd5957097df3eb6e4cb04cfb", + "decrypted_mac_hex": "4136481f858193f83f6c5468b3617acf7dfba3db2a325aa33017d885e5a31085" + } + ] +} diff --git a/crates/memory-testing/run_test.sh b/crates/memory-testing/run_test.sh new file mode 100755 index 000000000..627e5dacd --- /dev/null +++ b/crates/memory-testing/run_test.sh @@ -0,0 +1,20 @@ +# Move to the root of the repository +cd "$(dirname "$0")" +cd ../../ + +BASE_DIR="./crates/memory-testing" + +mkdir -p $BASE_DIR/output +rm $BASE_DIR/output/* + +cargo build -p memory-testing + +if [ "$1" = "no-docker" ]; then + # This specifically needs to run as root to be able to capture core dumps + sudo ./target/debug/capture-dumps ./target/debug/memory-testing $BASE_DIR +else + docker build -f crates/memory-testing/Dockerfile -t bitwarden/memory-testing . + docker run --rm -it -v $BASE_DIR:/output bitwarden/memory-testing +fi + +./target/debug/analyze-dumps $BASE_DIR diff --git a/crates/memory-testing/src/bin/analyze-dumps.rs b/crates/memory-testing/src/bin/analyze-dumps.rs new file mode 100644 index 000000000..9957bc1cd --- /dev/null +++ b/crates/memory-testing/src/bin/analyze-dumps.rs @@ -0,0 +1,130 @@ +use std::{env, fmt::Display, io, path::Path, process}; + +use memory_testing::*; + +fn find_subarrays(needle: &[u8], haystack: &[u8]) -> Vec { + let needle_len = needle.len(); + let haystack_len = haystack.len(); + let mut subarrays = vec![]; + + if needle_len == 0 || haystack_len == 0 || needle_len > haystack_len { + return vec![]; + } + + for i in 0..=(haystack_len - needle_len) { + if &haystack[i..i + needle_len] == needle { + subarrays.push(i); + } + } + + subarrays +} + +const OK: &str = "✅"; +const FAIL: &str = "❌"; + +fn comma_sep(nums: &[usize]) -> String { + nums.iter() + .map(ToString::to_string) + .collect::>() + .join(", ") +} + +fn add_row( + table: &mut comfy_table::Table, + name: N, + initial_pos: &[usize], + final_pos: &[usize], + ok_cond: bool, +) -> bool { + table.add_row(vec![ + name.to_string(), + comma_sep(initial_pos), + comma_sep(final_pos), + if ok_cond { + OK.to_string() + } else { + FAIL.to_string() + }, + ]); + !ok_cond +} + +fn main() -> io::Result<()> { + let args: Vec = env::args().collect(); + if args.len() < 2 { + println!("Usage: ./analyze-dumps "); + process::exit(1); + } + let base_dir: &Path = args[1].as_ref(); + + println!("Memory testing script started"); + + let initial_core = std::fs::read(base_dir.join("output/initial_dump.bin"))?; + let final_core = std::fs::read(base_dir.join("output/final_dump.bin"))?; + + let mut error = false; + let mut table = comfy_table::Table::new(); + table.set_header(vec!["Name", "Initial", "Final", "OK"]); + + let cases = memory_testing::load_cases(base_dir); + + let test_string: Vec = TEST_STRING.as_bytes().to_vec(); + let test_initial_pos = find_subarrays(&test_string, &initial_core); + let test_final_pos = find_subarrays(&test_string, &final_core); + + error |= add_row( + &mut table, + "Test String", + &test_initial_pos, + &test_final_pos, + !test_final_pos.is_empty(), + ); + + if test_initial_pos.is_empty() { + println!("ERROR: Test string not found in initial core dump, is the dump valid?"); + error = true; + } + + for (idx, case) in cases.symmetric_key.iter().enumerate() { + let key_part: Vec = hex::decode(&case.decrypted_key_hex).unwrap(); + let mac_part: Vec = hex::decode(&case.decrypted_mac_hex).unwrap(); + let key_in_b64: Vec = case.key.as_bytes().to_vec(); + + let key_initial_pos = find_subarrays(&key_part, &initial_core); + let mac_initial_pos = find_subarrays(&mac_part, &initial_core); + let b64_initial_pos = find_subarrays(&key_in_b64, &initial_core); + + let key_final_pos = find_subarrays(&key_part, &final_core); + let mac_final_pos = find_subarrays(&mac_part, &final_core); + let b64_final_pos = find_subarrays(&key_in_b64, &final_core); + + error |= add_row( + &mut table, + format!("Symm. Key, case {}", idx), + &key_initial_pos, + &key_final_pos, + key_final_pos.is_empty(), + ); + + error |= add_row( + &mut table, + format!("Symm. MAC, case {}", idx), + &mac_initial_pos, + &mac_final_pos, + mac_final_pos.is_empty(), + ); + + error |= add_row( + &mut table, + format!("Symm. Key in Base64, case {}", idx), + &b64_initial_pos, + &b64_final_pos, + b64_final_pos.is_empty(), + ); + } + + println!("{table}"); + + process::exit(if error { 1 } else { 0 }); +} diff --git a/crates/memory-testing/src/bin/capture-dumps.rs b/crates/memory-testing/src/bin/capture-dumps.rs new file mode 100644 index 000000000..f43905867 --- /dev/null +++ b/crates/memory-testing/src/bin/capture-dumps.rs @@ -0,0 +1,70 @@ +use std::{ + fs, + io::{self, prelude::*}, + path::Path, + process::{Command, Stdio}, + thread::sleep, + time::Duration, +}; + +fn dump_process_to_bytearray(pid: u32, output_dir: &Path, output_name: &Path) -> io::Result { + Command::new("gcore") + .args(["-a", &pid.to_string()]) + .output()?; + + let core_path = format!("core.{}", pid); + let output_path = output_dir.join(output_name); + let len = fs::copy(&core_path, output_path)?; + fs::remove_file(&core_path)?; + Ok(len) +} + +fn main() -> io::Result<()> { + let args: Vec = std::env::args().collect(); + if args.len() < 3 { + println!("Usage: ./capture_dumps "); + std::process::exit(1); + } + + let binary_path = &args[1]; + let base_dir: &Path = args[2].as_ref(); + + println!("Memory dump capture script started"); + + let mut proc = Command::new(binary_path) + .arg(base_dir) + .stdout(Stdio::inherit()) + .stdin(Stdio::piped()) + .spawn()?; + let id = proc.id(); + println!("Started memory testing process with PID: {}", id); + let stdin = proc.stdin.as_mut().expect("Valid stdin"); + + // Wait a bit for it to process + sleep(Duration::from_secs(3)); + + // Dump the process before the variables are freed + let initial_core = + dump_process_to_bytearray(id, &base_dir.join("output"), "initial_dump.bin".as_ref())?; + println!("Initial core dump file size: {}", initial_core); + + stdin.write_all(b".")?; + stdin.flush()?; + + // Wait a bit for it to process + sleep(Duration::from_secs(1)); + + // Dump the process after the variables are freed + let final_core = + dump_process_to_bytearray(id, &base_dir.join("output"), "final_dump.bin".as_ref())?; + println!("Final core dump file size: {}", final_core); + + stdin.write_all(b".")?; + stdin.flush()?; + + // Wait for the process to finish and print the output + let output = proc.wait()?; + println!("Return code: {}", output); + + std::process::exit(output.code().unwrap_or(1)); +} diff --git a/crates/memory-testing/src/lib.rs b/crates/memory-testing/src/lib.rs new file mode 100644 index 000000000..e633756d1 --- /dev/null +++ b/crates/memory-testing/src/lib.rs @@ -0,0 +1,29 @@ +use std::path::Path; + +use zeroize::Zeroize; + +pub const TEST_STRING: &str = "THIS IS USED TO CHECK THAT THE MEMORY IS DUMPED CORRECTLY"; + +pub fn load_cases(base_dir: &Path) -> Cases { + let mut json_str = std::fs::read_to_string(base_dir.join("cases.json")).unwrap(); + let cases: Cases = serde_json::from_str(&json_str).unwrap(); + + // Make sure that we don't leave extra copies of the data in memory + json_str.zeroize(); + cases +} + +// Note: We don't actively zeroize these structs here because we want the code in bitwarden_crypto +// to handle it for us +#[derive(serde::Deserialize)] +pub struct Cases { + pub symmetric_key: Vec, +} + +#[derive(serde::Deserialize)] +pub struct SymmetricKeyCases { + pub key: String, + + pub decrypted_key_hex: String, + pub decrypted_mac_hex: String, +} diff --git a/crates/memory-testing/src/main.rs b/crates/memory-testing/src/main.rs new file mode 100644 index 000000000..ee781683c --- /dev/null +++ b/crates/memory-testing/src/main.rs @@ -0,0 +1,45 @@ +use std::{env, io::Read, path::Path, process}; + +use bitwarden_crypto::{SensitiveString, SymmetricCryptoKey}; + +fn wait_for_dump() { + println!("Waiting for dump..."); + std::io::stdin().read_exact(&mut [1u8]).unwrap(); +} + +fn main() { + let args: Vec = env::args().collect(); + if args.len() < 2 { + println!("Usage: ./memory-testing "); + process::exit(1); + } + let base_dir: &Path = args[1].as_ref(); + + let test_string = String::from(memory_testing::TEST_STRING); + + let cases = memory_testing::load_cases(base_dir); + + let mut symmetric_keys = Vec::new(); + let mut symmetric_keys_as_vecs = Vec::new(); + + for case in cases.symmetric_key { + let key = SensitiveString::new(Box::new(case.key)); + let key = SymmetricCryptoKey::try_from(key).unwrap(); + symmetric_keys_as_vecs.push(key.to_vec()); + symmetric_keys.push(key); + } + + // Make a memory dump before the variables are freed + wait_for_dump(); + + // Use all the variables so the compiler doesn't decide to remove them + println!("{test_string} {symmetric_keys:?} {symmetric_keys_as_vecs:?}"); + + drop(symmetric_keys); + drop(symmetric_keys_as_vecs); + + // After the variables are dropped, we want to make another dump + wait_for_dump(); + + println!("Done!") +} diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml index c6de9eaca..3c5e0d50b 100644 --- a/crates/sdk-schemas/Cargo.toml +++ b/crates/sdk-schemas/Cargo.toml @@ -1,8 +1,15 @@ [package] name = "sdk-schemas" version = "0.1.0" -edition = "2021" -rust-version = "1.57" +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 [features] internal = [ @@ -12,11 +19,10 @@ internal = [ ] [dependencies] -schemars = { version = "0.8.12", features = ["preserve_order"] } -serde_json = "1.0.96" -anyhow = "1.0.71" -itertools = "0.11.0" - -bitwarden = { path = "../bitwarden" } +anyhow = "1.0.81" +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"] } +serde_json = "1.0.113" diff --git a/crates/sdk-schemas/src/main.rs b/crates/sdk-schemas/src/main.rs index 9d7e52a47..f9f074485 100644 --- a/crates/sdk-schemas/src/main.rs +++ b/crates/sdk-schemas/src/main.rs @@ -1,11 +1,11 @@ use std::{fs::File, io::Write}; use anyhow::Result; -use itertools::Itertools; -use schemars::{schema::RootSchema, schema_for}; +use schemars::{schema::RootSchema, schema_for, JsonSchema}; -/// Creates a json schema file for any type passed in using Schemars. The filename and path of the generated -/// schema file is derived from the namespace passed into the macro or supplied as the first argument. +/// Creates a json schema file for any type passed in using Schemars. The filename and path of the +/// generated schema file is derived from the namespace passed into the macro or supplied as the +/// first argument. /// /// The schema filename is given by the last namespace element and trims off any `>` characters. /// This means the filename will represent the last _generic_ type of the type given. @@ -31,7 +31,8 @@ use schemars::{schema::RootSchema, schema_for}; /// ``` /// write_schema_for!(response::two_factor_login_response::two_factor_providers::TwoFactorProviders); /// ``` -/// will generate `TwoFactorProviders.json` at `{{pwd}}/response/two_factor_login_response/TwoFactorProviders.json` +/// will generate `TwoFactorProviders.json` at +/// `{{pwd}}/response/two_factor_login_response/TwoFactorProviders.json` /// /// ## Path specified /// @@ -45,6 +46,8 @@ use schemars::{schema::RootSchema, schema_for}; /// will generate `Response.json` at `{{pwd}}/path/to/folder/Response.json` macro_rules! write_schema_for { ($type:ty) => { + use itertools::Itertools; + let schema = schema_for!($type); let type_name = stringify!($type); @@ -65,12 +68,6 @@ macro_rules! write_schema_for { }; } -macro_rules! write_schema_for_response { - ( $($type:ty),+ $(,)? ) => { - $( write_schema_for!("response", bitwarden_json::response::Response<$type>); )+ - }; -} - fn write_schema(schema: RootSchema, dir_path: String, type_name: String) -> Result<()> { let file_name = type_name .split("::") @@ -88,33 +85,39 @@ fn write_schema(schema: RootSchema, dir_path: String, type_name: String) -> Resu Ok(()) } -fn main() -> Result<()> { +use bitwarden_json::response::Response; + +#[allow(dead_code)] +#[derive(JsonSchema)] +struct SchemaTypes { // Input types for new Client - write_schema_for!(bitwarden::client::client_settings::ClientSettings); + client_settings: bitwarden::client::client_settings::ClientSettings, + // Input types for Client::run_command - write_schema_for!(bitwarden_json::command::Command); + input_command: bitwarden_json::command::Command, // Output types for Client::run_command - // Only add structs which are direct results of SDK commands. - write_schema_for_response! { - bitwarden::auth::login::ApiKeyLoginResponse, - bitwarden::auth::login::PasswordLoginResponse, - bitwarden::secrets_manager::secrets::SecretIdentifiersResponse, - bitwarden::secrets_manager::secrets::SecretResponse, - bitwarden::secrets_manager::secrets::SecretsResponse, - bitwarden::secrets_manager::secrets::SecretsDeleteResponse, - bitwarden::secrets_manager::projects::ProjectResponse, - bitwarden::secrets_manager::projects::ProjectsResponse, - bitwarden::secrets_manager::projects::ProjectsDeleteResponse, - }; + api_key_login: Response, + password_login: Response, + access_token_login: Response, + secret_identifiers: Response, + secret: Response, + secrets: Response, + secrets_delete: Response, + project: Response, + projects: Response, + projects_delete: Response, - // Same as above, but for the internal feature #[cfg(feature = "internal")] - write_schema_for_response! { - bitwarden::platform::FingerprintResponse, - bitwarden::platform::SyncResponse, - bitwarden::platform::UserApiKeyResponse, - }; + fingerprint: Response, + #[cfg(feature = "internal")] + sync: Response, + #[cfg(feature = "internal")] + user_api_key: Response, +} + +fn main() -> Result<()> { + write_schema_for!("schema_types", SchemaTypes); #[cfg(feature = "internal")] write_schema_for!(bitwarden_uniffi::docs::DocRef); diff --git a/crates/uniffi-bindgen/Cargo.toml b/crates/uniffi-bindgen/Cargo.toml index c9c177c84..5ef3977bc 100644 --- a/crates/uniffi-bindgen/Cargo.toml +++ b/crates/uniffi-bindgen/Cargo.toml @@ -1,8 +1,15 @@ [package] name = "uniffi-bindgen" version = "0.1.0" -edition = "2021" -rust-version = "1.57" +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 [[bin]] # This can be whatever name makes sense for your project, but the rest of this tutorial assumes uniffi-bindgen. @@ -10,4 +17,4 @@ name = "uniffi-bindgen" path = "uniffi-bindgen.rs" [dependencies] -uniffi = { version = "=0.24.1", features = ["cli"] } +uniffi = { version = "=0.26.1", features = ["cli"] } diff --git a/languages/cpp/CMakeBuild.md b/languages/cpp/CMakeBuild.md new file mode 100644 index 000000000..4c7c29814 --- /dev/null +++ b/languages/cpp/CMakeBuild.md @@ -0,0 +1,33 @@ +# CMAKE build + +## INTRODUCTION + +Cmake is used to build the c++ Bitwarden client library. Output should be placed in the build directory. The output contains two dynamic libraries: one that we are building `BitwardenClient` and another that the building library uses `bitwarden_c`. + +## PREREQUISITES + +- Cmake installed, minimum version 3.15 +- `schemas.hpp` generated into `include` directory +- installed `nlohmann-json` library +- installed `boost` library + +## BUILD commands + +One should be in the root directory of the c++ wrapper (the same level where is CMakeLists.txt placed). Paths of the three libraries should be placed inside the cmake build command: + +$ mkdir build +$ cd build +$ cmake .. -DNLOHMANN=/path/to/include/nlohmann -DBOOST=/path/to/include/boost -DTARGET=relative/path/to/libbitwarden_c +$ cmake --build . + + + +## Example + +macOS: + +$ mkdir build +$ cd build +$ cmake .. -DNLOHMANN=/opt/hombrew/include -DBOOST=/opt/homebrew/include -DTARGET=../../target/release/libbitwarden_c.dylib +$ cmake --build . + diff --git a/languages/cpp/CMakeLists.txt b/languages/cpp/CMakeLists.txt new file mode 100644 index 000000000..e513a32ed --- /dev/null +++ b/languages/cpp/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.15) +project(BitwardenClient) + +set(CMAKE_CXX_STANDARD 20) + +# Set placeholders to be passed from command line +set(NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER ${NLOHMANN}) +set(BOOST_INCLUDE_DIR_PLACEHOLDER ${BOOST}) +set(TARGET_INCLUDE_DIR_PLACEHOLDER ${TARGET}) + +# Specify the locations of nlohmann.json and Boost libraries +find_path(NLOHMANN_JSON_INCLUDE_DIR nlohmann/json.hpp HINTS ${NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER}) +find_path(BOOST_INCLUDE_DIR boost/optional.hpp HINTS ${BOOST_INCLUDE_DIR_PLACEHOLDER}) + +# Include directories for library +include_directories(include ${NLOHMANN_JSON_INCLUDE_DIR} ${BOOST_INCLUDE_DIR}) + +# Add library source files +file(GLOB SOURCES "src/*.cpp") + +# Add library source files along with the schemas.cpp file +add_library(BitwardenClient SHARED ${SOURCES} ${SCHEMAS_SOURCE}) + +# Set path for native library loading +set(LIB_BITWARDEN_C "${CMAKE_SOURCE_DIR}/${TARGET}") + +# Copy the library to the build directory before building +add_custom_command( + TARGET BitwardenClient PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${LIB_BITWARDEN_C} + $ +) + +# Link libraries +target_link_libraries(BitwardenClient PRIVATE ${LIB_BITWARDEN_C}) diff --git a/languages/cpp/ExampleUse.md b/languages/cpp/ExampleUse.md new file mode 100644 index 000000000..579cd0e67 --- /dev/null +++ b/languages/cpp/ExampleUse.md @@ -0,0 +1,70 @@ +# EXAMPLES + + +## PREREQUISITES + +### BITWARDEN Libraries +One should have two libraries at the same path: +- `BitwardeClient` +- `bitwarden_c` + +It should look like `libBitwardeClient.dylib` and `libbitwarden_c.dylib` for the macOS. + +For Linux: `libBitwardeClient.so` and `libbitwarden_c.so` +For Windows: `BitwardeClient.dll` and `bitwarden_c.dll` + +### INCLUDE directory + +`include` directory contains: +- `BitwardenLibrary.h` +- `BitwardenClient.h` +- `BitwardenSettings.h` +- `CommandRunner.h` +- `Projects.h` +- `Secrets.h` +- `schemas.hpp` + +### Other libraries +- `nlohmann-json` (https://github.com/nlohmann/json) +- `boost` (https://www.boost.org/) + + +### COMPILING + +One could use g++/clang++ for compiling. +Example of the folder structure (macOS): + +--root + --build + `libBitwardenClient.dylib` + `libbitwarden_c.dylib` + --include + --`BitwardenLibrary.h` + --`BitwardenClient.h` + --`BitwardenSettings.h` + --`CommandRunner.h` + --`Projects.h` + --`Secrets.h` + --`schemas.hpp` + --examples + --`Wrapper.cpp` + + +1. $ export ACCESS_TOKEN=<"access-token"> +2. $ export ORGANIZATION_ID=<"organization-id"> +3. $ export DYLD_LIBRARY_PATH=/path/to/your/library:$DYLD_LIBRARY_PATH + +The last step is neccessary to add the path for the dynamic library (macOS). +For the Linux one should use: +$ export LD_LIBRARY_PATH=/path/to/your/library:$LD_LIBRARY_PATH +For the Windows: +$ set PATH=%PATH%;C:\path\to\your\library + +4. $ cd examples +5. $ clang++ -std=c++20 -I../include -I/path/to/include/nlohmann -I/path/to/include/boost -L../build/ -o MyBitwardenApp Wrapper.cpp -lBitwardenClient -ldl + +for Windows `-ldl` should be excluded, + +The result is `MyBitwardenApp` in the `examples` directory, and one can run it from the `examples` directory: + +6. $ ./MyBitwardenApp diff --git a/languages/cpp/README.md b/languages/cpp/README.md new file mode 100644 index 000000000..23b59ac76 --- /dev/null +++ b/languages/cpp/README.md @@ -0,0 +1,97 @@ +# Bitwarden Secrets Manager SDK + +C++ bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some functionality. + +## Create access token + +Review the help documentation on [Access Tokens] + +## Usage code snippets + +### Client settings + +```c++ +// Optional - if not stressed, then default values are used +BitwardenSettings bitwardenSettings; +bitwardenSettings.set_api_url(""); +bitwardenSettings.set_identity_url(""); +``` + + +### Create new Bitwarden client + +```c++ +std::string accessToken = ""; +// Optional - argument in BitwardenClient +BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); +bitwardenClient.accessTokenLogin(accessToken); +``` + +### Create new project + +```c++ +boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(""); +ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "TestProject"); +``` + +### List all projects + +```c++ +ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid); +``` + +### Get project details + +```c++ +boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); +ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId); +``` + +### Update project + +```c++ +boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); +ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "TestProjectUpdated"); +``` + +### Delete projects + +```c++ +SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); +``` + +### Add new secret + +```c++ +std::string key = "key"; +std::string value = "value"; +std::string note = "note"; +SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); +``` + +### List secrets + +```c++ +SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid); +``` + +### Get secret details + +``` +boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); +SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); +``` + +### Update secret +```c++ +SecretResponse secretResponseUpdate = bitwardenClient.updateSecret(secretId, "key2", "value2", "note2", organizationUuid, {projectId}); +``` + +# Delete secrets + +```c++ +SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); +``` + +[Access Tokens]: https://bitwarden.com/help/access-tokens/ +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/cpp/examples/Wrapper.cpp b/languages/cpp/examples/Wrapper.cpp new file mode 100644 index 000000000..e94980698 --- /dev/null +++ b/languages/cpp/examples/Wrapper.cpp @@ -0,0 +1,74 @@ +#include "BitwardenClient.h" +#include +#include + +int main() { + // Retrieve access token and organization ID from environment variables + const char* accessTokenEnv = std::getenv("ACCESS_TOKEN"); + const char* organizationIdEnv = std::getenv("ORGANIZATION_ID"); + + const char* apiUrl = std::getenv("API_URL"); + const char* identityUrl = std::getenv("IDENTITY_URL"); + + if (!accessTokenEnv || !organizationIdEnv) { + std::cerr << "Error: Environment variables ACCESS_TOKEN or ORGANIZATION_ID not set." << std::endl; + return 1; + } + + std::string accessToken = accessTokenEnv; + std::string organizationId = organizationIdEnv; + + // Configuring the URLS is optional, remove them to use the default values + BitwardenSettings bitwardenSettings; + bitwardenSettings.set_api_url(apiUrl); + bitwardenSettings.set_identity_url(identityUrl); + + // Create a Bitwarden client instance + BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); + // // Access token login + bitwardenClient.accessTokenLogin(accessToken); + // Organization ID + boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(organizationId); + + // // Create a new project + ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "NewTestProject"); + boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); + + // List projects + ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid); + + // Get project details + ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId); + + // Update project + ProjectResponse ProjectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "NewTestProject2"); + + // Secrets + std::string key = "key"; + std::string value = "value"; + std::string note = "note"; + + // Create a new secret + SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); + boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); + + // List secrets + SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid); + + // Get secret details + SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); + + // Update secret + key = "key2"; + value = "value2"; + note = "note2"; + SecretResponse responseForSecretResponseUpdate = bitwardenClient.updateSecret(secretId, key, value, note, organizationUuid, {projectId}); + + // Delete secrets + SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); + + // Delete projects + ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.deleteProjects({projectId}); + + return 0; +} diff --git a/languages/cpp/include/BitwardenClient.h b/languages/cpp/include/BitwardenClient.h new file mode 100644 index 000000000..a5cf72475 --- /dev/null +++ b/languages/cpp/include/BitwardenClient.h @@ -0,0 +1,36 @@ +#pragma once + +#include "CommandRunner.h" +#include "BitwardenSettings.h" +#include "Projects.h" +#include "Secrets.h" +#include +#include + +class BitwardenClient { +public: + BitwardenClient(const BitwardenSettings& bitwardenSettings = BitwardenSettings()); + ~BitwardenClient(); + + void accessTokenLogin(const std::string& accessToken); + ProjectResponse getProject(const boost::uuids::uuid& id); + ProjectResponse createProject(const boost::uuids::uuid& organizationId, const std::string& name); + ProjectResponse updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name); + ProjectsDeleteResponse deleteProjects(const std::vector& ids); + ProjectsResponse listProjects(const boost::uuids::uuid &organizationId); + SecretResponse getSecret(const boost::uuids::uuid& id); + SecretResponse createSecret(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretResponse updateSecret(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretsDeleteResponse deleteSecrets(const std::vector& ids); + SecretIdentifiersResponse listSecrets(const boost::uuids::uuid& organizationId); + +private: + BitwardenLibrary* library; + void* client; + CommandRunner* commandRunner; + Projects projects; + Secrets secrets; + bool isClientOpen; + ClientSettings clientSettings; + +}; diff --git a/languages/cpp/include/BitwardenLibrary.h b/languages/cpp/include/BitwardenLibrary.h new file mode 100644 index 000000000..5fee78726 --- /dev/null +++ b/languages/cpp/include/BitwardenLibrary.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +class BitwardenLibrary { +public: + BitwardenLibrary(const std::string& providedLibraryPath); + ~BitwardenLibrary(); + + void* init(const char* clientSettingsJson); + void free_mem(void* client); + const char* run_command(const char* commandJson, void* client); + +private: +#ifdef _WIN32 + HMODULE libraryHandle; +#else + void* libraryHandle; +#endif +}; + diff --git a/languages/cpp/include/BitwardenSettings.h b/languages/cpp/include/BitwardenSettings.h new file mode 100644 index 000000000..4d075ed0a --- /dev/null +++ b/languages/cpp/include/BitwardenSettings.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class BitwardenSettings { +public: + BitwardenSettings() = default; + ~BitwardenSettings() = default; + + const std::string& get_api_url() const { return api_url; } + void set_api_url(const std::string& value) { api_url = value; } + + const std::string& get_identity_url() const { return identity_url; } + void set_identity_url(const std::string& value) { identity_url = value; } + +private: + std::string api_url; + std::string identity_url; +}; diff --git a/languages/cpp/include/CommandRunner.h b/languages/cpp/include/CommandRunner.h new file mode 100644 index 000000000..9aa6cbe9c --- /dev/null +++ b/languages/cpp/include/CommandRunner.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include "BitwardenLibrary.h" +#include "schemas.hpp" +#include + +using namespace Bitwarden::Sdk; + +class CommandRunner { +public: + CommandRunner(BitwardenLibrary* library, void* client); + + template + R runCommand(const Command& command, Func deserializer); + + + +private: + BitwardenLibrary* library; + void* client; + + std::string commandToString(const Command& command); + nlohmann::json filterNullObjects(const nlohmann::json& input); +}; + +template +R CommandRunner::runCommand(const Command& command, Func deserializer) { + // Serialize the Command object to a JSON string + std::string jsonString = commandToString(command); + const char* jsonCStr = jsonString.c_str(); + const char* response = library->run_command(jsonCStr, client); + + // Deserialize the response using the provided deserializer function + T deserialized = deserializer(response); + + // Unwrap the response and throw an exception if it was not successful + if (!deserialized.get_success()) { + throw std::runtime_error(*deserialized.get_error_message()); + } + + return deserialized.get_data().get(); +} + diff --git a/languages/cpp/include/Projects.h b/languages/cpp/include/Projects.h new file mode 100644 index 000000000..9bef19b9c --- /dev/null +++ b/languages/cpp/include/Projects.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include "CommandRunner.h" + +class Projects { +public: + Projects(CommandRunner* commandRunner); + + ProjectResponse get(const boost::uuids::uuid& id); + ProjectResponse create(const boost::uuids::uuid& organizationId, const std::string& name); + ProjectResponse update(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name); + ProjectsDeleteResponse deleteProjects(const std::vector& ids); + ProjectsResponse list(const boost::uuids::uuid& organizationId); + +private: + CommandRunner* commandRunner; +}; diff --git a/languages/cpp/include/Secrets.h b/languages/cpp/include/Secrets.h new file mode 100644 index 000000000..024ec3692 --- /dev/null +++ b/languages/cpp/include/Secrets.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include "CommandRunner.h" + +class Secrets { +public: + Secrets(CommandRunner* commandRunner); + + SecretResponse get(const boost::uuids::uuid& id); + SecretResponse create(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretResponse update(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretsDeleteResponse deleteSecrets(const std::vector& ids); + SecretIdentifiersResponse list(const boost::uuids::uuid& organizationId); + +private: + CommandRunner* commandRunner; +}; + diff --git a/languages/cpp/src/BitwardenClient.cpp b/languages/cpp/src/BitwardenClient.cpp new file mode 100644 index 000000000..fef9ea267 --- /dev/null +++ b/languages/cpp/src/BitwardenClient.cpp @@ -0,0 +1,145 @@ +#include "BitwardenClient.h" +#include +#include + +BitwardenClient::BitwardenClient(const BitwardenSettings& bitwardenSettings) + : library(nullptr), commandRunner(nullptr), isClientOpen(false), projects(nullptr), secrets(nullptr) { + + // Set default values for optional strings + boost::optional apiUrl = bitwardenSettings.get_api_url().empty() + ? boost::optional("https://api.bitwarden.com") + : boost::optional(bitwardenSettings.get_api_url()); + + boost::optional identityUrl = bitwardenSettings.get_identity_url().empty() + ? boost::optional("https://identity.bitwarden.com") + : boost::optional(bitwardenSettings.get_identity_url()); + + boost::optional user_agent = boost::optional("Bitwarden CPP-SDK"); + + // Set values in clientSettings + clientSettings.set_device_type(Bitwarden::Sdk::DeviceType::SDK); + clientSettings.set_user_agent(user_agent); + clientSettings.set_api_url(apiUrl); + clientSettings.set_identity_url(identityUrl); + + nlohmann::json jsonClientSettings; + Bitwarden::Sdk::to_json(jsonClientSettings, clientSettings); + + std::string jsonClientSettingsString = jsonClientSettings.dump(); + const char* jsonClientSettingsCStr = jsonClientSettingsString.c_str(); + + try { + library = new BitwardenLibrary("./"); + client = library->init(jsonClientSettingsCStr); + commandRunner = new CommandRunner(library, client); + projects = Projects(commandRunner); + secrets = Secrets(commandRunner); + isClientOpen = true; + } catch (const std::exception& ex) { + std::cerr << "Failed to initialize: " << ex.what() << std::endl; + throw ex; + } +} + +BitwardenClient::~BitwardenClient() { + if (library) { + delete commandRunner; + library->free_mem(client); + delete library; + isClientOpen = false; + } +} + +void BitwardenClient::accessTokenLogin(const std::string& accessToken) { + Command command; + AccessTokenLoginRequest accessTokenLoginRequest; + accessTokenLoginRequest.set_access_token(accessToken); + command.set_access_token_login(accessTokenLoginRequest); + + auto deserializer = [](const char* response) -> ResponseForApiKeyLoginResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForApiKeyLoginResponse loginResponse; + Bitwarden::Sdk::from_json(jsonResponse, loginResponse); + return loginResponse; + }; + try { + commandRunner->runCommand(command, deserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in accessTokenLogin: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectResponse BitwardenClient::getProject(const boost::uuids::uuid& id){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.get(id); +} + +ProjectResponse BitwardenClient::createProject(const boost::uuids::uuid& organizationId, const std::string& name){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.create(organizationId, name); +} + +ProjectResponse BitwardenClient::updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.update(id, organizationId, name); +} + +ProjectsDeleteResponse BitwardenClient::deleteProjects(const std::vector& ids) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.deleteProjects(ids); + +} + +ProjectsResponse BitwardenClient::listProjects(const boost::uuids::uuid &organizationId) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return projects.list(organizationId); + +} + +SecretResponse BitwardenClient::getSecret(const boost::uuids::uuid& id){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.get(id); +} + +SecretResponse BitwardenClient::createSecret(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.create(key, value, note, organizationId, projectIds); +} + +SecretResponse BitwardenClient::updateSecret(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.update(id, key, value, note, organizationId, projectIds); +} + +SecretsDeleteResponse BitwardenClient::deleteSecrets(const std::vector& ids) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.deleteSecrets(ids); + +} + +SecretIdentifiersResponse BitwardenClient::listSecrets(const boost::uuids::uuid &organizationId) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.list(organizationId); + +} diff --git a/languages/cpp/src/BitwardenLibrary.cpp b/languages/cpp/src/BitwardenLibrary.cpp new file mode 100644 index 000000000..0af592786 --- /dev/null +++ b/languages/cpp/src/BitwardenLibrary.cpp @@ -0,0 +1,107 @@ +#include "BitwardenLibrary.h" +#include + +BitwardenLibrary::BitwardenLibrary(const std::string& providedLibraryPath) : libraryHandle(nullptr) { + std::string libraryExtension; + std::string libraryNameUnix = "libbitwarden_c"; + std::string libraryNameWin = "bitwarden_c"; +#if defined(_WIN32) + libraryExtension = ".dll"; +#elif defined(__linux__) + libraryExtension = ".so"; +#elif defined(__APPLE__) + libraryExtension = ".dylib"; +#else + // Unsupported platform + std::cerr << "Unsupported platform." << std::endl; + return; +#endif + + // Load the dynamic library +#ifdef _WIN32 + std::string libraryPath = providedLibraryPath + libraryNameWin + libraryExtension; + // Load the dynamic library on Windows + libraryHandle = LoadLibraryA(libraryPath.c_str()); + + if (!libraryHandle) { + std::cerr << "Failed to load the Bitwarden library." << std::endl; + } +#else + std::string libraryPath = providedLibraryPath + libraryNameUnix + libraryExtension; + // Load the dynamic library on Unix-based systems (Linux, macOS) + libraryHandle = dlopen(libraryPath.c_str(), RTLD_NOW); + + if (!libraryHandle) { + std::cerr << "Failed to load the Bitwarden library: " << dlerror() << std::endl; + } +#endif +} + +BitwardenLibrary::~BitwardenLibrary() { + if (libraryHandle) { +#ifdef _WIN32 + FreeLibrary(libraryHandle); +#else + dlclose(libraryHandle); +#endif + } +} + +void* BitwardenLibrary::init(const char* clientSettingsJson) { + typedef void* (*InitFunction)(const char*); + InitFunction initFunction = nullptr; + +#ifdef _WIN32 + // Get the address of the init function on Windows + initFunction = reinterpret_cast(GetProcAddress(libraryHandle, "init")); +#else + // Get the address of the init function on Unix-based systems + initFunction = reinterpret_cast(dlsym(libraryHandle, "init")); +#endif + + if (initFunction) { + return initFunction(clientSettingsJson); + } + + std::cerr << "Failed to load init function from the Bitwarden library: " << std::endl; + return nullptr; +} + +void BitwardenLibrary::free_mem(void* client) { + typedef void (*FreeMemFunction)(void*); + FreeMemFunction freeMemFunction = nullptr; + +#ifdef _WIN32 + // Get the address of the free_mem function on Windows + freeMemFunction = reinterpret_cast(GetProcAddress(libraryHandle, "free_mem")); +#else + // Get the address of the free_mem function on Unix-based systems + freeMemFunction = reinterpret_cast(dlsym(libraryHandle, "free_mem")); +#endif + + if (freeMemFunction) { + freeMemFunction(client); + } else { + std::cerr << "Failed to load free_mem function from the Bitwarden library." << std::endl; + } +} + +const char* BitwardenLibrary::run_command(const char* commandJson, void* client) { + typedef const char* (*RunCommandFunction)(const char*, void*); + RunCommandFunction runCommandFunction = nullptr; + +#ifdef _WIN32 + // Get the address of the run_command function on Windows + runCommandFunction = reinterpret_cast(GetProcAddress(libraryHandle, "run_command")); +#else + // Get the address of the run_command function on Unix-based systems + runCommandFunction = reinterpret_cast(dlsym(libraryHandle, "run_command")); +#endif + + if (runCommandFunction) { + return runCommandFunction(commandJson, client); + } + + std::cerr << "Failed to load run_command function from the Bitwarden library." << std::endl; + return nullptr; +} diff --git a/languages/cpp/src/CommandRunner.cpp b/languages/cpp/src/CommandRunner.cpp new file mode 100644 index 000000000..032347f34 --- /dev/null +++ b/languages/cpp/src/CommandRunner.cpp @@ -0,0 +1,49 @@ +#include "CommandRunner.h" +#include +#include +#include + + +CommandRunner::CommandRunner(BitwardenLibrary* library, void* client) : library(library), client(client) {} + +// Function to recursively filter out objects with all null values +nlohmann::json CommandRunner::filterNullObjects(const nlohmann::json& input) { + nlohmann::json result; + + for (auto it = input.begin(); it != input.end(); ++it) { + if (!it.value().is_null()) { + if (it.value().is_object()) { + // Recursively filter nested objects + json nestedFiltered = filterNullObjects(it.value()); + if (!nestedFiltered.empty()) { + result[it.key()] = nestedFiltered; + } + } else { + result[it.key()] = it.value(); + } + } + } + + return result; +} + +// Implement the commandToString function +std::string CommandRunner::commandToString(const Command& command) { + try { + // Create an nlohmann::json object from the Command object + nlohmann::json jsonCommand; + nlohmann::json filteredJsonCommand; + + Bitwarden::Sdk::to_json(jsonCommand, command); + + filteredJsonCommand = filterNullObjects(jsonCommand); + + // Convert the JSON to a string + std::string jsonCommandString = filteredJsonCommand.dump(); + + return jsonCommandString; + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/languages/cpp/src/Projects.cpp b/languages/cpp/src/Projects.cpp new file mode 100644 index 000000000..d0aa6ed49 --- /dev/null +++ b/languages/cpp/src/Projects.cpp @@ -0,0 +1,132 @@ +#include "Projects.h" +#include +#include +#include +#include +#include + +Projects::Projects(CommandRunner* commandRunner) : commandRunner(commandRunner) {} + +auto projectsDeserializer = [](const char* response) -> ResponseForProjectResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForProjectResponse projectResponse; + Bitwarden::Sdk::from_json(jsonResponse, projectResponse); + return projectResponse; +}; + +auto deleteProjectsDeserializer = [](const char* response) -> ResponseForProjectsDeleteResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForProjectsDeleteResponse deleteProjectsResponse; + Bitwarden::Sdk::from_json(jsonResponse, deleteProjectsResponse); + return deleteProjectsResponse; +}; + +auto projectListDeserializer = [](const char* response) -> ResponseForProjectsResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForProjectsResponse listResponse; + Bitwarden::Sdk::from_json(jsonResponse, listResponse); + return listResponse; +}; + +ProjectResponse Projects::get(const boost::uuids::uuid& id) { + Command command; + ProjectsCommand projectsCommand; + ProjectGetRequest projectGetRequest; + + std::string idStr = boost::uuids::to_string(id); + projectGetRequest.set_id(idStr); + + projectsCommand.set_get(projectGetRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in getProject: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectResponse Projects::create(const boost::uuids::uuid& organizationId, const std::string& name) { + Command command; + ProjectsCommand projectsCommand; + ProjectCreateRequest projectCreateRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + projectCreateRequest.set_organization_id(orgIdStr); + + projectCreateRequest.set_name(name); + projectsCommand.set_create(projectCreateRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in createProject: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectResponse Projects::update(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name) { + Command command; + ProjectsCommand projectsCommand; + ProjectPutRequest projectPutRequest; + + std::string idStr = boost::uuids::to_string(id); + projectPutRequest.set_id(idStr); + + std::string orgIdStr = boost::uuids::to_string(organizationId); + projectPutRequest.set_organization_id(orgIdStr); + + projectPutRequest.set_name(name); + projectsCommand.set_update(projectPutRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in updateProject: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectsDeleteResponse Projects::deleteProjects(const std::vector& ids) { + Command command; + ProjectsCommand projectsCommand; + ProjectsDeleteRequest projectsDeleteRequest; + + std::vector idStrs; + for (const auto& id : ids) { + idStrs.push_back(boost::uuids::to_string(id)); + } + projectsDeleteRequest.set_ids(idStrs); + + projectsCommand.set_projects_command_delete(projectsDeleteRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, deleteProjectsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in deleteProjects: " << ex.what() << std::endl; + throw ex; + } +} + +ProjectsResponse Projects::list(const boost::uuids::uuid& organizationId) { + Command command; + ProjectsCommand projectsCommand; + ProjectsListRequest projectsListRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + projectsListRequest.set_organization_id(orgIdStr); + + projectsCommand.set_list(projectsListRequest); + command.set_projects(projectsCommand); + + try { + return commandRunner->runCommand(command, projectListDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in listProjects: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/languages/cpp/src/Secrets.cpp b/languages/cpp/src/Secrets.cpp new file mode 100644 index 000000000..e153ea7f1 --- /dev/null +++ b/languages/cpp/src/Secrets.cpp @@ -0,0 +1,149 @@ +#include "Secrets.h" +#include +#include +#include +#include + +Secrets::Secrets(CommandRunner* commandRunner) : commandRunner(commandRunner) {} + +auto secretsDeserializer = [](const std::string& response) -> ResponseForSecretResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretResponse secretResponse; + Bitwarden::Sdk::from_json(jsonResponse, secretResponse); + return secretResponse; +}; + +auto deleteSecretsDeserializer = [](const std::string& response) -> ResponseForSecretsDeleteResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretsDeleteResponse deleteSecretsResponse; + Bitwarden::Sdk::from_json(jsonResponse, deleteSecretsResponse); + return deleteSecretsResponse; +}; + +auto secretListDeserializer = [](const std::string& response) -> ResponseForSecretIdentifiersResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretIdentifiersResponse listResponse; + Bitwarden::Sdk::from_json(jsonResponse, listResponse); + return listResponse; +}; + +SecretResponse Secrets::get(const boost::uuids::uuid& id) { + Command command; + SecretsCommand secretsCommand; + SecretGetRequest secretGetRequest; + + std::string idStr = boost::uuids::to_string(id); + secretGetRequest.set_id(idStr); + + secretsCommand.set_get(secretGetRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in getSecret: " << ex.what() << std::endl; + throw ex; + } +} + +SecretResponse Secrets::create(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds) { + Command command; + SecretsCommand secretsCommand; + SecretCreateRequest secretCreateRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretCreateRequest.set_organization_id(orgIdStr); + + secretCreateRequest.set_key(key); + secretCreateRequest.set_value(value); + secretCreateRequest.set_note(note); + + std::vector projectIdsStr; + for (const auto& projectId : projectIds) { + projectIdsStr.push_back(boost::uuids::to_string(projectId)); + } + secretCreateRequest.set_project_ids(projectIdsStr); + + secretsCommand.set_create(secretCreateRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in createSecret: " << ex.what() << std::endl; + throw ex; + } +} + +SecretResponse Secrets::update(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds) { + Command command; + SecretsCommand secretsCommand; + SecretPutRequest secretPutRequest; + + std::string idStr = boost::uuids::to_string(id); + secretPutRequest.set_id(idStr); + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretPutRequest.set_organization_id(orgIdStr); + + secretPutRequest.set_key(key); + secretPutRequest.set_value(value); + secretPutRequest.set_note(note); + + std::vector projectIdsStr; + for (const auto& projectId : projectIds) { + projectIdsStr.push_back(boost::uuids::to_string(projectId)); + } + secretPutRequest.set_project_ids(projectIdsStr); + + secretsCommand.set_update(secretPutRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in updateSecret: " << ex.what() << std::endl; + throw ex; + } +} + +SecretsDeleteResponse Secrets::deleteSecrets(const std::vector& ids) { + Command command; + SecretsCommand secretsCommand; + SecretsDeleteRequest secretsDeleteRequest; + + std::vector idsStr; + for (const auto& id : ids) { + idsStr.push_back(boost::uuids::to_string(id)); + } + secretsDeleteRequest.set_ids(idsStr); + + secretsCommand.set_secrets_command_delete(secretsDeleteRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, deleteSecretsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in deleteSecrets: " << ex.what() << std::endl; + throw ex; + } +} + +SecretIdentifiersResponse Secrets::list(const boost::uuids::uuid& organizationId) { + Command command; + SecretsCommand secretsCommand; + SecretIdentifiersRequest secretIdentifiersRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretIdentifiersRequest.set_organization_id(orgIdStr); + + secretsCommand.set_list(secretIdentifiersRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretListDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in listSecret: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj index 97f850211..35770eedf 100644 --- a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj +++ b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj @@ -1,73 +1,77 @@ - - net6.0 - README.md - enable - enable - Bitwarden.Sdk - Bitwarden Secrets Manager SDK - Bitwarden Inc. - .NET bindings for interacting with the Bitwarden Secrets Manager - Bitwarden Inc. - https://github.com/bitwarden/sdk/tree/master/languages/csharp - Bitwarden;Sdk;.NET - SDK - bitwarden.png - Git - Bitwarden.Sdk - https://bitwarden.com/products/secrets-manager/ - https://github.com/bitwarden/sdk/blob/master/LICENSE - + + net6.0 + enable + enable + Bitwarden.Sdk - - - - - - + Bitwarden Secrets Manager SDK + Bitwarden Inc. + .NET bindings for interacting with the Bitwarden Secrets Manager + Bitwarden Inc. + SDK - - - + https://github.com/bitwarden/sdk/tree/main/languages/csharp + Git - - 4 - - - - Always - true - - - Always - true - - - Always - true - - - - - Always - true - runtimes/osx-x64/native - - - Always - true - runtimes/osx-arm64/native - - - Always - true - runtimes/linux-x64/native - - - Always - true - runtimes/win-x64/native - - - + https://bitwarden.com/products/secrets-manager/ + Bitwarden.Secrets.Sdk + bitwarden.png + Bitwarden;Secrets Manager;Sdk;.NET + README.md + LICENSE.txt + 0.1.0 + + + + + + + + + + + + + + + 4 + + + + Always + true + + + Always + true + + + Always + true + + + + + Always + true + runtimes/osx-x64/native + + + Always + true + runtimes/osx-arm64/native + + + Always + true + runtimes/linux-x64/native + + + Always + true + runtimes/win-x64/native + + + \ No newline at end of file diff --git a/languages/csharp/global.json b/languages/csharp/global.json index 527fd31d3..10b65be86 100644 --- a/languages/csharp/global.json +++ b/languages/csharp/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.413", + "version": "6.0.100", "rollForward": "latestFeature" } } diff --git a/languages/go/.version b/languages/go/.version new file mode 100644 index 000000000..6c6aa7cb0 --- /dev/null +++ b/languages/go/.version @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file diff --git a/languages/go/README.md b/languages/go/README.md new file mode 100644 index 000000000..cea02e282 --- /dev/null +++ b/languages/go/README.md @@ -0,0 +1,115 @@ +# Bitwarden SDK in Go + +This SDK is designed to interact with Bitwarden services in Go. It includes implementations for +managing projects and secrets, as well as a client interface to facilitate operations like login. + +## Prerequisites + +- Go installed +- C environment to run CGO + +## Installation + +Download the SDK files and place them in your Go project directory. + +## Table of Contents + +- [Initialization](#initialization) +- [Login](#login) +- [Projects](#projects) +- [Secrets](#secrets) +- [Close Client](#close-client) + +--- + +### Initialization + +To initialize the client, you need to import the SDK and create a new `BitwardenClient` instance. + +```go +import "github.com/bitwarden/sdk/languages/go" + +bitwardenClient, _ := sdk.NewBitwardenClient(&apiURL, &identityURL) +``` + +--- + +### Login + +To login using an access token. Define some `statePath` and pass it to use state, or pass `nil` instead to not use state. + +```go +statePath := os.Getenv("STATE_PATH") + +err := bitwardenClient.AccessTokenLogin(accessToken, &statePath) +``` + +--- + +### Projects + +#### Create a Project + +```go +project, err := client.Projects.Create("organization_id", "project_name") +``` + +#### List Projects + +```go +projects, err := client.Projects.List("organization_id") +``` + +#### Update a Project + +```go +project, err := client.Projects.Update("project_id", "organization_id", "new_project_name") +``` + +#### Delete Projects + +```go +project, err := client.Projects.Delete([]string{"project_id_1", "project_id_2"}) +``` + +--- + +### Secrets + +#### Create a Secret + +```go +secret, err := client.Secrets.Create("key", "value", "note", "organization_id", []string{"project_id"}) +``` + +#### List Secrets + +```go +secrets, err := client.Secrets.List("organization_id") +``` + +#### Update a Secret + +```go +secret, err := client.Secrets.Update("secret_id", "new_key", "new_value", "new_note", "organization_id", []string{"project_id"}) +``` + +#### Delete Secrets + +```go +secret, err := client.Secrets.Delete([]string{"secret_id_1", "secret_id_2"}) +``` + +--- + +### Close Client + +To free up resources: + +```go +defer bitwardenClient.Close() +``` + +--- + +For more detailed information, refer to the code comments and method signatures. diff --git a/languages/go/bitwarden_client.go b/languages/go/bitwarden_client.go new file mode 100644 index 000000000..bd236de71 --- /dev/null +++ b/languages/go/bitwarden_client.go @@ -0,0 +1,63 @@ +package sdk + +import ( + "encoding/json" + + "github.com/bitwarden/sdk/languages/go/internal/cinterface" +) + +type BitwardenClient struct { + client cinterface.ClientPointer + lib cinterface.BitwardenLibrary + commandRunner CommandRunnerInterface + Projects ProjectsInterface + Secrets SecretsInterface +} + +func NewBitwardenClient(apiURL *string, identityURL *string) (*BitwardenClient, error) { + deviceType := DeviceType("SDK") + userAgent := "Bitwarden GOLANG-SDK" + clientSettings := ClientSettings{ + APIURL: apiURL, + IdentityURL: identityURL, + UserAgent: &userAgent, + DeviceType: &deviceType, + } + + settingsJSON, err := json.Marshal(clientSettings) + if err != nil { + return nil, err + } + + lib := cinterface.NewBitwardenLibrary() + client, err := lib.Init(string(settingsJSON)) + if err != nil { + return nil, err + } + runner := NewCommandRunner(client, lib) + + return &BitwardenClient{ + lib: lib, + client: client, + commandRunner: runner, + Projects: NewProjects(runner), + Secrets: NewSecrets(runner), + }, nil +} + +func (c *BitwardenClient) AccessTokenLogin(accessToken string, statePath *string) error { + req := AccessTokenLoginRequest{AccessToken: accessToken, StateFile: statePath} + command := Command{AccessTokenLogin: &req} + + responseStr, err := c.commandRunner.RunCommand(command) + if err != nil { + return err + } + + var response APIKeyLoginResponse + return checkSuccessAndError(responseStr, &response) +} + +func (c *BitwardenClient) Close() { + c.lib.FreeMem(c.client) +} diff --git a/languages/go/command_runner.go b/languages/go/command_runner.go new file mode 100644 index 000000000..3f79f7149 --- /dev/null +++ b/languages/go/command_runner.go @@ -0,0 +1,37 @@ +package sdk + +import ( + "encoding/json" + + "github.com/bitwarden/sdk/languages/go/internal/cinterface" +) + +type CommandRunnerInterface interface { + RunCommand(command Command) (string, error) +} + +type CommandRunner struct { + client cinterface.ClientPointer + lib cinterface.BitwardenLibrary +} + +func NewCommandRunner(client cinterface.ClientPointer, lib cinterface.BitwardenLibrary) *CommandRunner { + return &CommandRunner{ + client: client, + lib: lib, + } +} + +func (c *CommandRunner) RunCommand(command Command) (string, error) { + commandJSON, err := json.Marshal(command) + if err != nil { + return "", err + } + + responseStr, err := c.lib.RunCommand(string(commandJSON), c.client) + if err != nil { + return "", err + } + + return responseStr, nil +} diff --git a/languages/go/example/example.go b/languages/go/example/example.go new file mode 100644 index 000000000..eb4d34612 --- /dev/null +++ b/languages/go/example/example.go @@ -0,0 +1,116 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + sdk "github.com/bitwarden/sdk/languages/go" + "github.com/gofrs/uuid" +) + +func main() { + // Configuring the URLS is optional, set them to nil to use the default values + apiURL := os.Getenv("API_URL") + identityURL := os.Getenv("IDENTITY_URL") + + bitwardenClient, _ := sdk.NewBitwardenClient(&apiURL, &identityURL) + + accessToken := os.Getenv("ACCESS_TOKEN") + organizationIDStr := os.Getenv("ORGANIZATION_ID") + projectName := os.Getenv("PROJECT_NAME") + + // Configuring the statePath is optional, pass nil + // in AccessTokenLogin() to not use state + statePath := os.Getenv("STATE_PATH") + + if projectName == "" { + projectName = "NewTestProject" // default value + } + + err := bitwardenClient.AccessTokenLogin(accessToken, &statePath) + if err != nil { + panic(err) + } + + organizationID, err := uuid.FromString(organizationIDStr) + if err != nil { + panic(err) + } + + project, err := bitwardenClient.Projects.Create(organizationID.String(), projectName) + if err != nil { + panic(err) + } + fmt.Println(project) + projectID := project.ID + fmt.Println(projectID) + + if _, err = bitwardenClient.Projects.List(organizationID.String()); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Projects.Get(projectID); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Projects.Update(projectID, organizationID.String(), projectName+"2"); err != nil { + panic(err) + } + + key := "key" + value := "value" + note := "note" + + secret, err := bitwardenClient.Secrets.Create(key, value, note, organizationID.String(), []string{projectID}) + if err != nil { + panic(err) + } + secretID := secret.ID + + if _, err = bitwardenClient.Secrets.List(organizationID.String()); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Secrets.Get(secretID); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Secrets.Update(secretID, key, value, note, organizationID.String(), []string{projectID}); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Secrets.Delete([]string{secretID}); err != nil { + panic(err) + } + + if _, err = bitwardenClient.Projects.Delete([]string{projectID}); err != nil { + panic(err) + } + + secretIdentifiers, err := bitwardenClient.Secrets.List(organizationID.String()) + if err != nil { + panic(err) + } + + // Get secrets with a list of IDs + secretIDs := make([]string, len(secretIdentifiers.Data)) + for i, identifier := range secretIdentifiers.Data { + secretIDs[i] = identifier.ID + } + + secrets, err := bitwardenClient.Secrets.GetByIDS(secretIDs) + if err != nil { + log.Fatalf("Error getting secrets: %v", err) + } + + jsonSecrets, err := json.MarshalIndent(secrets, "", " ") + if err != nil { + log.Fatalf("Error marshalling secrets to JSON: %v", err) + } + + fmt.Println(string(jsonSecrets)) + + defer bitwardenClient.Close() +} diff --git a/languages/go/example/go.mod b/languages/go/example/go.mod new file mode 100644 index 000000000..bbde28fd5 --- /dev/null +++ b/languages/go/example/go.mod @@ -0,0 +1,10 @@ +module example + +replace github.com/bitwarden/sdk/languages/go => ../ + +go 1.20 + +require ( + github.com/bitwarden/sdk/languages/go v0.0.0-00010101000000-000000000000 + github.com/gofrs/uuid v4.4.0+incompatible +) diff --git a/languages/go/example/go.sum b/languages/go/example/go.sum new file mode 100644 index 000000000..c0ad68738 --- /dev/null +++ b/languages/go/example/go.sum @@ -0,0 +1,2 @@ +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= diff --git a/languages/go/go.mod b/languages/go/go.mod new file mode 100644 index 000000000..a9e125453 --- /dev/null +++ b/languages/go/go.mod @@ -0,0 +1,5 @@ +module github.com/bitwarden/sdk/languages/go + +go 1.18 + +require github.com/gofrs/uuid v4.4.0+incompatible diff --git a/languages/go/go.sum b/languages/go/go.sum new file mode 100644 index 000000000..c0ad68738 --- /dev/null +++ b/languages/go/go.sum @@ -0,0 +1,2 @@ +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= diff --git a/languages/go/internal/cinterface/bitwarden_library.go b/languages/go/internal/cinterface/bitwarden_library.go new file mode 100644 index 000000000..42327adb2 --- /dev/null +++ b/languages/go/internal/cinterface/bitwarden_library.go @@ -0,0 +1,55 @@ +package cinterface + +import ( + "fmt" + "unsafe" +) + +/* +#cgo LDFLAGS: -lbitwarden_c +#cgo linux LDFLAGS: -L/usr/local/lib -L/usr/lib -L ./lib +#cgo darwin LDFLAGS: -L/usr/local/lib -L/usr/lib -L ./lib +#include +typedef void* ClientPtr; +extern char* run_command(const char *command, ClientPtr client); +extern ClientPtr init(const char *clientSettings); +extern void free_mem(ClientPtr client); +*/ +import "C" + +type ClientPointer struct { + Pointer C.ClientPtr +} + +type BitwardenLibrary interface { + Init(clientSettings string) (ClientPointer, error) + FreeMem(client ClientPointer) + RunCommand(command string, client ClientPointer) (string, error) +} + +type BitwardenLibraryImpl struct{} + +func NewBitwardenLibrary() BitwardenLibrary { + return &BitwardenLibraryImpl{} +} + +func (b *BitwardenLibraryImpl) Init(clientSettings string) (ClientPointer, error) { + ptr := C.init(C.CString(clientSettings)) + if ptr == nil { + return ClientPointer{}, fmt.Errorf("initialization failed") + } + return ClientPointer{Pointer: ptr}, nil +} + +func (b *BitwardenLibraryImpl) FreeMem(client ClientPointer) { + C.free_mem(client.Pointer) +} + +func (b *BitwardenLibraryImpl) RunCommand(command string, client ClientPointer) (string, error) { + cstr := C.run_command(C.CString(command), client.Pointer) + if cstr == nil { + return "", fmt.Errorf("run command failed") + } + defer C.free(unsafe.Pointer(cstr)) + return C.GoString(cstr), nil +} diff --git a/languages/go/project.go b/languages/go/project.go new file mode 100644 index 000000000..24a30a7ac --- /dev/null +++ b/languages/go/project.go @@ -0,0 +1,107 @@ +package sdk + +type ProjectsInterface interface { + Create(organizationID string, name string) (*ProjectResponse, error) + List(organizationID string) (*ProjectsResponse, error) + Get(projectID string) (*ProjectResponse, error) + Update(projectID string, organizationID string, name string) (*ProjectResponse, error) + Delete(projectIDs []string) (*ProjectsDeleteResponse, error) +} + +type Projects struct { + CommandRunner CommandRunnerInterface +} + +func NewProjects(commandRunner CommandRunnerInterface) *Projects { + return &Projects{CommandRunner: commandRunner} +} + +func (p *Projects) Get(id string) (*ProjectResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Get: &ProjectGetRequest{ + ID: id, + }, + }, + } + var response ProjectResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) Create(organizationID string, name string) (*ProjectResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Create: &ProjectCreateRequest{ + OrganizationID: organizationID, + Name: name, + }, + }, + } + + var response ProjectResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) List(organizationID string) (*ProjectsResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + List: &ProjectsListRequest{ + OrganizationID: organizationID, + }, + }, + } + + var response ProjectsResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) Update(projectID, organizationID, name string) (*ProjectResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Update: &ProjectPutRequest{ + ID: projectID, + OrganizationID: organizationID, + Name: name, + }, + }, + } + + var response ProjectResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) Delete(projectIDs []string) (*ProjectsDeleteResponse, error) { + command := Command{ + Projects: &ProjectsCommand{ + Delete: &ProjectsDeleteRequest{ + IDS: projectIDs, + }, + }, + } + + var response ProjectsDeleteResponse + if err := p.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (p *Projects) executeCommand(command Command, target interface{}) error { + responseStr, err := p.CommandRunner.RunCommand(command) + if err != nil { + return err + } + return checkSuccessAndError(responseStr, target) +} diff --git a/languages/go/secrets.go b/languages/go/secrets.go new file mode 100644 index 000000000..2c56538f7 --- /dev/null +++ b/languages/go/secrets.go @@ -0,0 +1,131 @@ +package sdk + +type SecretsInterface interface { + Create(key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) + List(organizationID string) (*SecretIdentifiersResponse, error) + Get(secretID string) (*SecretResponse, error) + GetByIDS(secretIDs []string) (*SecretsResponse, error) + Update(secretID string, key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) + Delete(secretIDs []string) (*SecretsDeleteResponse, error) +} + +type Secrets struct { + CommandRunner CommandRunnerInterface +} + +func NewSecrets(commandRunner CommandRunnerInterface) *Secrets { + return &Secrets{CommandRunner: commandRunner} +} + +func (s *Secrets) executeCommand(command Command, target interface{}) error { + responseStr, err := s.CommandRunner.RunCommand(command) + if err != nil { + return err + } + return checkSuccessAndError(responseStr, target) +} + +func (s *Secrets) Create(key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Create: &SecretCreateRequest{ + Key: key, + Value: value, + Note: note, + OrganizationID: organizationID, + ProjectIDS: projectIDs, + }, + }, + } + + var response SecretResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) List(organizationID string) (*SecretIdentifiersResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + List: &SecretIdentifiersRequest{ + OrganizationID: organizationID, + }, + }, + } + + var response SecretIdentifiersResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) Get(id string) (*SecretResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Get: &SecretGetRequest{ + ID: id, + }, + }, + } + + var response SecretResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) GetByIDS(ids []string) (*SecretsResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + GetByIDS: &SecretsGetRequest{ + IDS: ids, + }, + }, + } + + var response SecretsResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) Update(id string, key, value, note string, organizationID string, projectIDs []string) (*SecretResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Update: &SecretPutRequest{ + ID: id, + Key: key, + Value: value, + Note: note, + OrganizationID: organizationID, + ProjectIDS: projectIDs, + }, + }, + } + + var response SecretResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} + +func (s *Secrets) Delete(ids []string) (*SecretsDeleteResponse, error) { + command := Command{ + Secrets: &SecretsCommand{ + Delete: &SecretsDeleteRequest{ + IDS: ids, + }, + }, + } + + var response SecretsDeleteResponse + if err := s.executeCommand(command, &response); err != nil { + return nil, err + } + return &response, nil +} diff --git a/languages/go/util.go b/languages/go/util.go new file mode 100644 index 000000000..01ab3578d --- /dev/null +++ b/languages/go/util.go @@ -0,0 +1,33 @@ +package sdk + +import ( + "encoding/json" + "fmt" +) + +func checkSuccessAndError(responseStr string, v interface{}) error { + var wrapper struct { + Success bool `json:"success"` + ErrorMessage *string `json:"errorMessage"` + Data *json.RawMessage `json:"data"` + } + + err := json.Unmarshal([]byte(responseStr), &wrapper) + if err != nil { + return fmt.Errorf("failed to unmarshal wrapper response: %v", err) + } + + if !wrapper.Success { + if wrapper.ErrorMessage != nil { + return fmt.Errorf("API error: %s", *wrapper.ErrorMessage) + } + return fmt.Errorf("API error: unknown") + } + + err = json.Unmarshal(*wrapper.Data, &v) + if err != nil { + return fmt.Errorf("failed to unmarshal response: %v", err) + } + + return nil +} diff --git a/languages/java/.gitignore b/languages/java/.gitignore new file mode 100644 index 000000000..b5b6c5697 --- /dev/null +++ b/languages/java/.gitignore @@ -0,0 +1,3 @@ +target +.gradle +src/main/resources diff --git a/languages/java/Example.java b/languages/java/Example.java new file mode 100644 index 000000000..eacf23f8a --- /dev/null +++ b/languages/java/Example.java @@ -0,0 +1,33 @@ +import java.lang.System; +import java.util.UUID; + +import com.bitwarden.sdk.*; +import com.bitwarden.sdk.schema.*; + +class Example { + public static void main(String[] args) { + + String accessToken = System.getenv("ACCESS_TOKEN"); + UUID organizationId = UUID.fromString(System.getenv("ORGANIZATION_ID")); + String apiUrl = System.getenv("API_URL"); + String identityUrl = System.getenv("IDENTITY_URL"); + + // Configuring the URLS is optional, remove them to use the default values + BitwardenSettings bitwardenSettings = new BitwardenSettings(); + bitwardenSettings.setApiUrl(apiUrl); + bitwardenSettings.setIdentityUrl(identityUrl); + BitwardenClient client = new BitwardenClient(bitwardenSettings); + client.accessTokenLogin(accessToken); + + + ProjectResponse project = client.projects().create(organizationId, "Test Project"); + ProjectsResponse list = client.projects().list(organizationId); + + SecretResponse secret = client.secrets().create("Secret Key", "Secret Value", "Secret Note", organizationId, new UUID[] { project.getID() }); + + System.out.println("Secret: " + secret.getValue()); + + client.secrets().delete(new UUID[] { secret.getID() }); + client.projects().delete(new UUID[] { project.getID() }); + } +} diff --git a/languages/java/README.md b/languages/java/README.md new file mode 100644 index 000000000..06f1ffec3 --- /dev/null +++ b/languages/java/README.md @@ -0,0 +1,73 @@ +# Bitwarden Secrets Manager SDK + +Java bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some +functionality. + +## Create access token + +Review the help documentation on [Access Tokens] + +## Usage code snippets + +### Create new Bitwarden client + +```java +BitwardenSettings bitwardenSettings = new BitwardenSettings(); +bitwardenSettings.setApiUrl("https://api.bitwarden.com"); +bitwardenSettings.setIdentityUrl("https://identity.bitwarden.com"); +BitwardenClient bitwardenClient = new BitwardenClient(bitwardenSettings); +bitwardenClient.accessTokenLogin(""); +``` + +### Create new project + +```java +UUID organizationId = UUID.fromString(""); +var projectResponse = bitwardenClient.projects().create(organizationId, "TestProject"); +``` + +### List all projects + +```java +var projectsResponse = bitwardenClient.projects().list(organizationId); +``` + +### Update project + +```java +UUID projectId = projectResponse.getID(); +projectResponse = bitwardenClient.projects().get(projectId); +projectResponse = bitwardenClient.projects.update(projectId, organizationId, "TestProjectUpdated"); +``` + +### Add new secret + +```java +String key = "key"; +String value = "value"; +String note = "note"; +var secretResponse = bitwardenClient.secrets().create(key, value, note, organizationId, new UUID[]{projectId}); +UUID secretId = secretResponse.getID(); +``` + +### Update secret + +```java +bitwardenClient.secrets().update(secretId, key2, value2, note2, organizationId, new UUID[]{projectId}); +``` + +### List secrets + +```java +var secretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.secrets().list(organizationId); +``` + +# Delete secret or project + +```java +bitwardenClient.secrets().delete(new UUID[]{secretId}); +bitwardenClient.projects().delete(new UUID[]{projectId}); +``` + +[Access Tokens]: https://bitwarden.com/help/access-tokens/ +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ \ No newline at end of file diff --git a/languages/java/build.gradle b/languages/java/build.gradle new file mode 100644 index 000000000..ed4a72f22 --- /dev/null +++ b/languages/java/build.gradle @@ -0,0 +1,90 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'java-library' + id 'maven-publish' +} + +repositories { + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } + + dependencies { + api 'com.fasterxml.jackson.core:jackson-core:2.9.10' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.10' + api 'net.java.dev.jna:jna-platform:5.12.1' + } + + description = 'BitwardenSDK' + java.sourceCompatibility = JavaVersion.VERSION_1_8 + + publishing { + publications { + maven(MavenPublication) { + groupId = 'com.bitwarden' + artifactId = 'sdk' + + // Determine the version from the git history. + // + // PRs: use the branch name. + // Main: Grab it from `crates/bitwarden/Cargo.toml` + + 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() + + version = "${matcher.group(1)}-SNAPSHOT" + } else { + // branchName-SNAPSHOT + version = "${branchName.replaceAll('/', '-')}-SNAPSHOT" + } + + afterEvaluate { + from components.java + } + } + } + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/bitwarden/sdk" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +tasks.withType(Javadoc) { + options.encoding = 'UTF-8' +} + +// Gradle build requires GitHub workflow to copy native library to resources +// Uncomment copyNativeLib and jar tasks to use the local build (modify architecture if needed) +//tasks.register('copyNativeLib', Copy) { +// delete 'src/main/resources/darwin-aarch64' +// from '../../target/debug' +// include '*libbitwarden_c*.dylib' +// include '*libbitwarden_c*.so' +// include '*bitwarden_c*.dll' +// into 'src/main/resources/darwin-aarch64' +//} +// +//jar { +// dependsOn tasks.named("copyNativeLib").get() +// from 'src/main/resources' +//} diff --git a/languages/java/gradle/wrapper/gradle-wrapper.jar b/languages/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..7f93135c4 Binary files /dev/null and b/languages/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/languages/java/gradle/wrapper/gradle-wrapper.properties b/languages/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ac72c34e8 --- /dev/null +++ b/languages/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/languages/java/gradlew b/languages/java/gradlew new file mode 100755 index 000000000..0adc8e1a5 --- /dev/null +++ b/languages/java/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/languages/java/gradlew.bat b/languages/java/gradlew.bat new file mode 100644 index 000000000..6689b85be --- /dev/null +++ b/languages/java/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/languages/java/settings.gradle b/languages/java/settings.gradle new file mode 100644 index 000000000..961795f50 --- /dev/null +++ b/languages/java/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'BitwardenSDK' diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java new file mode 100644 index 000000000..9e1948184 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClient.java @@ -0,0 +1,87 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +import java.util.function.Function; + +public class BitwardenClient implements AutoCloseable { + + private final Pointer client; + + private final BitwardenLibrary library; + + private final CommandRunner commandRunner; + + private boolean isClientOpen; + + private final ProjectsClient projects; + + private final SecretsClient secrets; + + public BitwardenClient(BitwardenSettings bitwardenSettings) { + ClientSettings clientSettings = new ClientSettings(); + clientSettings.setAPIURL(bitwardenSettings.getApiUrl()); + clientSettings.setIdentityURL(bitwardenSettings.getIdentityUrl()); + clientSettings.setDeviceType(DeviceType.SDK); + clientSettings.setUserAgent("Bitwarden JAVA-SDK"); + + library = Native.load("bitwarden_c", BitwardenLibrary.class); + + try { + client = library.init(Converter.ClientSettingsToJsonString(clientSettings)); + } catch (JsonProcessingException e) { + throw new BitwardenClientException("Error while processing client settings"); + } + + commandRunner = new CommandRunner(library, client); + projects = new ProjectsClient(commandRunner); + secrets = new SecretsClient(commandRunner); + isClientOpen = true; + } + + static Function throwingFunctionWrapper(ThrowingFunction throwingFunction) { + + return i -> { + try { + return throwingFunction.accept(i); + } catch (Exception ex) { + throw new BitwardenClientException("Response deserialization failed"); + } + }; + } + + public APIKeyLoginResponse accessTokenLogin(String accessToken) { + Command command = new Command(); + AccessTokenLoginRequest accessTokenLoginRequest = new AccessTokenLoginRequest(); + accessTokenLoginRequest.setAccessToken(accessToken); + command.setAccessTokenLogin(accessTokenLoginRequest); + + ResponseForAPIKeyLoginResponse response = commandRunner.runCommand(command, + throwingFunctionWrapper(Converter::ResponseForAPIKeyLoginResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Login failed"); + } + + return response.getData(); + } + + public ProjectsClient projects() { + return projects; + } + + public SecretsClient secrets() { + return secrets; + } + + @Override + public void close() { + if (isClientOpen) { + library.free_mem(client); + isClientOpen = false; + } + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java new file mode 100644 index 000000000..7b6025be8 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenClientException.java @@ -0,0 +1,8 @@ +package com.bitwarden.sdk; + +public class BitwardenClientException extends RuntimeException { + + public BitwardenClientException(String message) { + super(message); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenLibrary.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenLibrary.java new file mode 100644 index 000000000..73d81452c --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenLibrary.java @@ -0,0 +1,13 @@ +package com.bitwarden.sdk; + +import com.sun.jna.Library; +import com.sun.jna.Pointer; + +public interface BitwardenLibrary extends Library { + + Pointer init(String clientSettings); + + void free_mem(Pointer client); + + String run_command(String command, Pointer client); +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/BitwardenSettings.java b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenSettings.java new file mode 100644 index 000000000..7112297b4 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/BitwardenSettings.java @@ -0,0 +1,32 @@ +package com.bitwarden.sdk; + +public class BitwardenSettings { + + private String apiUrl; + + private String identityUrl; + + public BitwardenSettings() { + } + + public BitwardenSettings(String apiUrl, String identityUrl) { + this.apiUrl = apiUrl; + this.identityUrl = identityUrl; + } + + public String getApiUrl() { + return apiUrl; + } + + public void setApiUrl(String apiUrl) { + this.apiUrl = apiUrl; + } + + public String getIdentityUrl() { + return identityUrl; + } + + public void setIdentityUrl(String identityUrl) { + this.identityUrl = identityUrl; + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/CommandRunner.java b/languages/java/src/main/java/com/bitwarden/sdk/CommandRunner.java new file mode 100644 index 000000000..11a100814 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/CommandRunner.java @@ -0,0 +1,45 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.Command; +import com.bitwarden.sdk.schema.Converter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.jna.Pointer; + +import java.io.IOException; +import java.util.function.Function; + +class CommandRunner { + + private final BitwardenLibrary library; + + private final Pointer client; + + CommandRunner(BitwardenLibrary library, Pointer client) { + this.library = library; + this.client = client; + } + + T runCommand(Command command, Function deserializer) { + String response = null; + + try { + response = library.run_command(commandToString(command), client); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return deserializer.apply(response); + } + + private String commandToString(Command command) throws IOException { + // Removes null properties from the generated converter output to avoid command errors + String inputJson = Converter.CommandToJsonString(command); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + Object inputObject = mapper.readValue(inputJson, Object.class); + return mapper.writeValueAsString(inputObject); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java b/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java new file mode 100644 index 000000000..73b87440c --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/ProjectsClient.java @@ -0,0 +1,109 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; + +import java.util.UUID; + +public class ProjectsClient { + + private final CommandRunner commandRunner; + + ProjectsClient(CommandRunner commandRunner) { + this.commandRunner = commandRunner; + } + + public ProjectResponse get(UUID id) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectGetRequest projectGetRequest = new ProjectGetRequest(); + projectGetRequest.setID(id); + projectsCommand.setGet(projectGetRequest); + command.setProjects(projectsCommand); + + ResponseForProjectResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Project not found"); + } + + return response.getData(); + } + + public ProjectResponse create(UUID organizationId, String name) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectCreateRequest projectCreateRequest = new ProjectCreateRequest(); + projectCreateRequest.setOrganizationID(organizationId); + projectCreateRequest.setName(name); + projectsCommand.setCreate(projectCreateRequest); + command.setProjects(projectsCommand); + + ResponseForProjectResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Project create failed"); + } + + return response.getData(); + } + + public ProjectResponse update(UUID id, UUID organizationId, String name) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectPutRequest projectPutRequest = new ProjectPutRequest(); + projectPutRequest.setID(id); + projectPutRequest.setOrganizationID(organizationId); + projectPutRequest.setName(name); + projectsCommand.setUpdate(projectPutRequest); + command.setProjects(projectsCommand); + + ResponseForProjectResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Project update failed"); + } + + return response.getData(); + } + + public ProjectsDeleteResponse delete(UUID[] ids) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectsDeleteRequest projectsDeleteRequest = new ProjectsDeleteRequest(); + projectsDeleteRequest.setIDS(ids); + projectsCommand.setDelete(projectsDeleteRequest); + command.setProjects(projectsCommand); + + ResponseForProjectsDeleteResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectsDeleteResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? + response.getErrorMessage() : "Projects update failed"); + } + + return response.getData(); + } + + public ProjectsResponse list(UUID organizationId) { + Command command = new Command(); + ProjectsCommand projectsCommand = new ProjectsCommand(); + ProjectsListRequest projectsListRequest = new ProjectsListRequest(); + projectsListRequest.setOrganizationID(organizationId); + projectsCommand.setList(projectsListRequest); + command.setProjects(projectsCommand); + + ResponseForProjectsResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForProjectsResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? + response.getErrorMessage() : "No projects for given organization"); + } + + return response.getData(); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java b/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java new file mode 100644 index 000000000..f1a97fdc7 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/SecretsClient.java @@ -0,0 +1,115 @@ +package com.bitwarden.sdk; + +import com.bitwarden.sdk.schema.*; + +import java.util.UUID; + +public class SecretsClient { + + private final CommandRunner commandRunner; + + SecretsClient(CommandRunner commandRunner) { + this.commandRunner = commandRunner; + } + + public SecretResponse get(UUID id) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretGetRequest secretGetRequest = new SecretGetRequest(); + secretGetRequest.setID(id); + secretsCommand.setGet(secretGetRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secret not found"); + } + + return response.getData(); + } + + public SecretResponse create(String key, String value, String note, UUID organizationId, UUID[] projectIds) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretCreateRequest secretCreateRequest = new SecretCreateRequest(); + secretCreateRequest.setKey(key); + secretCreateRequest.setValue(value); + secretCreateRequest.setNote(note); + secretCreateRequest.setOrganizationID(organizationId); + secretCreateRequest.setProjectIDS(projectIds); + secretsCommand.setCreate(secretCreateRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secret create failed"); + } + + return response.getData(); + } + + public SecretResponse update(UUID id, String key, String value, String note, UUID organizationId, + UUID[] projectIds) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretPutRequest secretPutRequest = new SecretPutRequest(); + secretPutRequest.setID(id); + secretPutRequest.setKey(key); + secretPutRequest.setValue(value); + secretPutRequest.setNote(note); + secretPutRequest.setOrganizationID(organizationId); + secretPutRequest.setProjectIDS(projectIds); + secretsCommand.setUpdate(secretPutRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secret update failed"); + } + + return response.getData(); + } + + public SecretsDeleteResponse delete(UUID[] ids) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretsDeleteRequest secretsDeleteRequest = new SecretsDeleteRequest(); + secretsDeleteRequest.setIDS(ids); + secretsCommand.setDelete(secretsDeleteRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretsDeleteResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretsDeleteResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? response.getErrorMessage() : "Secrets delete failed"); + } + + return response.getData(); + } + + public SecretIdentifiersResponse list(UUID organizationId) { + Command command = new Command(); + SecretsCommand secretsCommand = new SecretsCommand(); + SecretIdentifiersRequest secretIdentifiersRequest = new SecretIdentifiersRequest(); + secretIdentifiersRequest.setOrganizationID(organizationId); + secretsCommand.setList(secretIdentifiersRequest); + command.setSecrets(secretsCommand); + + ResponseForSecretIdentifiersResponse response = commandRunner.runCommand(command, + BitwardenClient.throwingFunctionWrapper(Converter::ResponseForSecretIdentifiersResponseFromJsonString)); + + if (response == null || !response.getSuccess()) { + throw new BitwardenClientException(response != null ? + response.getErrorMessage() : "No secrets for given organization"); + } + + return response.getData(); + } +} diff --git a/languages/java/src/main/java/com/bitwarden/sdk/ThrowingFunction.java b/languages/java/src/main/java/com/bitwarden/sdk/ThrowingFunction.java new file mode 100644 index 000000000..cab5b1041 --- /dev/null +++ b/languages/java/src/main/java/com/bitwarden/sdk/ThrowingFunction.java @@ -0,0 +1,7 @@ +package com.bitwarden.sdk; + +@FunctionalInterface +public interface ThrowingFunction { + + R accept(T t) throws E; +} diff --git a/languages/js/example/index.js b/languages/js/example/index.js new file mode 100644 index 000000000..8da3fc582 --- /dev/null +++ b/languages/js/example/index.js @@ -0,0 +1,32 @@ +const { BitwardenClient: BitwardenClientWasm, LogLevel } = require("@bitwarden/sdk-wasm"); +const sdk = require("@bitwarden/sdk-client"); + +async function main() { + const settings = { + apiUrl: process.env.API_URL, + identityUrl: process.env.IDENTITY_URL, + }; + + const client = new sdk.BitwardenClient( + new BitwardenClientWasm(JSON.stringify(settings), LogLevel.Debug), + ); + + const organization_id = process.env.ORGANIZATION_ID; + await client.accessTokenLogin(process.env.ACCESS_TOKEN); + + const project = await client.projects().create("test", organization_id); + const projects = await client.projects().list(organization_id); + console.log(projects.data); + + const secret = await client + .secrets() + .create("test-secret", "My secret!", "This is my secret", [project.id], organization_id); + const secrets = await client.secrets().list(organization_id); + console.log(secrets.data); + + console.log(project, secret); + + await client.projects().delete([project.id]); + await client.secrets().delete([secret.id]); +} +main(); diff --git a/languages/js/example/package-lock.json b/languages/js/example/package-lock.json new file mode 100644 index 000000000..d48fd43be --- /dev/null +++ b/languages/js/example/package-lock.json @@ -0,0 +1,32 @@ +{ + "name": "sdk-example", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sdk-example", + "dependencies": { + "@bitwarden/sdk-client": "../sdk-client", + "@bitwarden/sdk-wasm": "../wasm" + } + }, + "../sdk-client": { + "devDependencies": { + "@types/node": "^18.15.11", + "rimraf": "^5.0.0", + "typescript": "^5.0.3" + } + }, + "../wasm": { + "version": "0.1.0" + }, + "node_modules/@bitwarden/sdk-client": { + "resolved": "../sdk-client", + "link": true + }, + "node_modules/@bitwarden/sdk-wasm": { + "resolved": "../wasm", + "link": true + } + } +} diff --git a/languages/js/example/package.json b/languages/js/example/package.json new file mode 100644 index 000000000..836e53085 --- /dev/null +++ b/languages/js/example/package.json @@ -0,0 +1,11 @@ +{ + "name": "sdk-example", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@bitwarden/sdk-client": "../sdk-client", + "@bitwarden/sdk-wasm": "../wasm" + } +} diff --git a/languages/js_webassembly/.gitignore b/languages/js/sdk-client/.gitignore similarity index 100% rename from languages/js_webassembly/.gitignore rename to languages/js/sdk-client/.gitignore diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json new file mode 100644 index 000000000..071d01578 --- /dev/null +++ b/languages/js/sdk-client/package-lock.json @@ -0,0 +1,535 @@ +{ + "name": "@bitwarden/sdk-client", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@bitwarden/sdk-client", + "devDependencies": { + "@types/node": "^18.15.11", + "rimraf": "^5.0.0", + "typescript": "^5.0.3" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/node": { + "version": "18.19.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", + "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "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, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/languages/js/sdk-client/package.json b/languages/js/sdk-client/package.json new file mode 100644 index 000000000..9e4998b4f --- /dev/null +++ b/languages/js/sdk-client/package.json @@ -0,0 +1,20 @@ +{ + "name": "@bitwarden/sdk-client", + "exports": { + ".": { + "types": "./dist/src/lib.d.ts", + "default": "./dist/src/lib.js" + } + }, + "main": "dist/src/lib.js", + "types": "dist/src/lib.d.ts", + "scripts": { + "build": "npm run clean && tsc", + "clean": "rimraf dist" + }, + "devDependencies": { + "@types/node": "^18.15.11", + "rimraf": "^5.0.0", + "typescript": "^5.0.3" + } +} diff --git a/languages/js/sdk-client/src/client.ts b/languages/js/sdk-client/src/client.ts new file mode 100644 index 000000000..0f06889c1 --- /dev/null +++ b/languages/js/sdk-client/src/client.ts @@ -0,0 +1,197 @@ +import { + Convert, + ProjectResponse, + ProjectsDeleteResponse, + ProjectsResponse, + SecretIdentifiersResponse, + SecretResponse, + SecretsDeleteResponse, +} from "./schemas"; + +interface BitwardenSDKClient { + run_command(js_input: string): Promise; +} + +function handleResponse(response: { success: boolean; errorMessage?: string; data?: T }): T { + if (!response.success) { + throw new Error(response.errorMessage); + } + return response.data as T; +} + +export class BitwardenClient { + client: BitwardenSDKClient; + + constructor(client: BitwardenSDKClient) { + this.client = client; + } + + async accessTokenLogin(accessToken: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + accessTokenLogin: { + accessToken, + }, + }), + ); + + handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); + } + + secrets(): SecretsClient { + return new SecretsClient(this.client); + } + + projects(): ProjectsClient { + return new ProjectsClient(this.client); + } +} + +export class SecretsClient { + client: BitwardenSDKClient; + + constructor(client: BitwardenSDKClient) { + this.client = client; + } + + async get(id: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + secrets: { + get: { id }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretResponse(response)); + } + + async create( + key: string, + value: string, + note: string, + projectIds: string[], + organizationId: string, + ): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + secrets: { + create: { key, value, note, projectIds, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretResponse(response)); + } + + async list(organizationId: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + secrets: { + list: { organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretIdentifiersResponse(response)); + } + + async update( + id: string, + key: string, + value: string, + note: string, + projectIds: string[], + organizationId: string, + ): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + secrets: { + update: { id, key, value, note, projectIds, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretResponse(response)); + } + + async delete(ids: string[]): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + secrets: { + delete: { ids }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretsDeleteResponse(response)); + } +} + +export class ProjectsClient { + client: BitwardenSDKClient; + + constructor(client: BitwardenSDKClient) { + this.client = client; + } + + async get(id: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + get: { id }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async create(name: string, organizationId: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + create: { name, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async list(organizationId: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + list: { organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectsResponse(response)); + } + + async update(id: string, name: string, organizationId: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + update: { id, name, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async delete(ids: string[]): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + delete: { ids }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectsDeleteResponse(response)); + } +} diff --git a/languages/js/sdk-client/src/lib.ts b/languages/js/sdk-client/src/lib.ts new file mode 100644 index 000000000..7fd93ce86 --- /dev/null +++ b/languages/js/sdk-client/src/lib.ts @@ -0,0 +1,2 @@ +export * from "./client"; +export * from "./schemas"; diff --git a/languages/js/sdk-client/tsconfig.json b/languages/js/sdk-client/tsconfig.json new file mode 100644 index 000000000..987e8d679 --- /dev/null +++ b/languages/js/sdk-client/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "module": "commonjs", + "target": "es5", + "moduleResolution": "node", + "sourceMap": true, + "composite": true, + "declarationMap": true + }, + "include": ["./src/**/*"], +} diff --git a/languages/js/wasm/.gitignore b/languages/js/wasm/.gitignore new file mode 100644 index 000000000..c4f6a47f4 --- /dev/null +++ b/languages/js/wasm/.gitignore @@ -0,0 +1,6 @@ +bitwarden_wasm_bg.js +bitwarden_wasm_bg.wasm +bitwarden_wasm_bg.wasm.d.ts +bitwarden_wasm_bg.wasm.js +bitwarden_wasm.d.ts +bitwarden_wasm.js diff --git a/languages/js/wasm/index.js b/languages/js/wasm/index.js new file mode 100644 index 000000000..c2b512005 --- /dev/null +++ b/languages/js/wasm/index.js @@ -0,0 +1,25 @@ +// https://stackoverflow.com/a/47880734 +const supported = (() => { + try { + if (typeof WebAssembly === "object" + && typeof WebAssembly.instantiate === "function") { + const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); + if (module instanceof WebAssembly.Module) + return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; + } + } catch (e) { + } + return false; +})(); + +let wasm; + +if (supported) { + wasm = await import('./bitwarden_wasm_bg.wasm'); +} else { + wasm = await import('./bitwarden_wasm_bg.wasm.js'); +} + +import { __wbg_set_wasm } from "./bitwarden_wasm_bg.js"; +__wbg_set_wasm(wasm); +export * from "./bitwarden_wasm_bg.js"; diff --git a/languages/js/wasm/package.json b/languages/js/wasm/package.json new file mode 100644 index 000000000..eadbb5fb3 --- /dev/null +++ b/languages/js/wasm/package.json @@ -0,0 +1,25 @@ +{ + "name": "@bitwarden/sdk-wasm", + "version": "0.1.0", + "files": [ + "bitwarden_wasm_bg.js", + "bitwarden_wasm_bg.wasm", + "bitwarden_wasm_bg.wasm.d.ts", + "bitwarden_wasm_bg.wasm.js", + "bitwarden_wasm.d.ts", + "bitwarden_wasm.js", + "index.js", + "node/bitwarden_wasm_bg.wasm", + "node/bitwarden_wasm_bg.wasm.d.ts", + "node/bitwarden_wasm.d.ts", + "node/bitwarden_wasm.js" + ], + "main": "node/bitwarden_wasm.js", + "module": "index.js", + "types": "bitwarden_wasm.d.ts", + "scripts": {}, + "sideEffects": [ + "./bitwarden_wasm.js", + "./snippets/*" + ] +} diff --git a/languages/js_webassembly/bitwarden_client/index.ts b/languages/js_webassembly/bitwarden_client/index.ts deleted file mode 100644 index 3f858fd39..000000000 --- a/languages/js_webassembly/bitwarden_client/index.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as rust from "../pkg/bitwarden_wasm"; -import { LoggingLevel } from "./logging_level"; -import { - ClientSettings, - Convert, - ResponseForPasswordLoginResponse, - ResponseForSecretIdentifiersResponse, - ResponseForSecretResponse, - ResponseForSecretsDeleteResponse, - ResponseForSyncResponse, - ResponseForUserAPIKeyResponse, -} from "./schemas"; - -export class BitwardenClient { - client: rust.BitwardenClient; - - constructor(settings?: ClientSettings, logging_level?: LoggingLevel) { - const settings_json = settings == null ? null : Convert.clientSettingsToJson(settings); - this.client = new rust.BitwardenClient(settings_json, logging_level ?? LoggingLevel.Info); - } - - async login(email: string, password: string): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - passwordLogin: { - email: email, - password: password, - }, - }) - ); - - return Convert.toResponseForPasswordLoginResponse(response); - } - - async getUserApiKey( - secret: string, - isOtp: boolean = false - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - getUserApiKey: { - masterPassword: isOtp ? null : secret, - otp: isOtp ? secret : null, - }, - }) - ); - - return Convert.toResponseForUserAPIKeyResponse(response); - } - - - async sync( - excludeSubdomains: boolean = false - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - sync: { - excludeSubdomains - }, - }) - ); - - return Convert.toResponseForSyncResponse(response); - } - - secrets(): SecretsClient { - return new SecretsClient(this.client); - } -} - -export class SecretsClient { - client: rust.BitwardenClient; - - constructor(client: rust.BitwardenClient) { - this.client = client; - } - - async get( - id: string - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - secrets: { - get: { id } - }, - }) - ); - - return Convert.toResponseForSecretResponse(response); - } - - async create( - key: string, - note: string, - organizationId: string, - value: string, - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - secrets: { - create: { key, note, organizationId, value } - }, - }) - ); - - return Convert.toResponseForSecretResponse(response); - } - - async list( - organizationId: string - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - secrets: { - list: { organizationId } - }, - }) - ); - - return Convert.toResponseForSecretIdentifiersResponse(response); - } - - async update( - id: string, - key: string, - note: string, - organizationId: string, - value: string, - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - secrets: { - update: { id, key, note, organizationId, value } - }, - }) - ); - - return Convert.toResponseForSecretResponse(response); - } - - async delete( - ids: string[] - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - secrets: { - delete: { ids } - }, - }) - ); - - return Convert.toResponseForSecretsDeleteResponse(response); - } - -} diff --git a/languages/js_webassembly/bitwarden_client/logging_level.ts b/languages/js_webassembly/bitwarden_client/logging_level.ts deleted file mode 100644 index 2453b1eb6..000000000 --- a/languages/js_webassembly/bitwarden_client/logging_level.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum LoggingLevel { - Trace, - Debug, - Info, - Warn, - Error, -} diff --git a/languages/js_webassembly/index.html b/languages/js_webassembly/index.html deleted file mode 100644 index 661517934..000000000 --- a/languages/js_webassembly/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/languages/js_webassembly/index.ts b/languages/js_webassembly/index.ts deleted file mode 100644 index 047a815bb..000000000 --- a/languages/js_webassembly/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { LoggingLevel } from "./bitwarden_client/logging_level"; -import { DeviceType } from "./bitwarden_client/schemas"; - -import("./bitwarden_client").then(async (module) => { - const client = new module.BitwardenClient({ - apiUrl: "http://localhost:8081/api", - identityUrl: "http://localhost:8081/identity", - deviceType: DeviceType.SDK, - userAgent: "Bitwarden JS SDK", - }, LoggingLevel.Debug); - const result = await client.login("test@bitwarden.com", "asdfasdf"); - console.log(`auth result success: ${result.success}`); - - const apikeyResponse = await client.getUserApiKey("asdfasdf"); - console.log(`user API key: ${apikeyResponse.data.apiKey}`); - - const sync = await client.sync(); - console.log("Sync result", sync); - - const org_id = sync.data.profile.organizations[0].id; - - const secret = await client.secrets().create("TEST_KEY", "This is a test secret", org_id, "Secret1234!"); - console.log("New secret: ", secret.data); - - await client.secrets().delete([secret.data.id]); -}); diff --git a/languages/js_webassembly/package-lock.json b/languages/js_webassembly/package-lock.json deleted file mode 100644 index da527fe32..000000000 --- a/languages/js_webassembly/package-lock.json +++ /dev/null @@ -1,4109 +0,0 @@ -{ - "name": "js_webassembly", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "html-webpack-plugin": "5.5.3", - "text-encoding": "0.7.0", - "ts-loader": "9.5.0", - "wasm-pack": "0.12.1", - "webpack": "5.88.2", - "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", - "dev": true - }, - "node_modules/@types/body-parser": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", - "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.11.tgz", - "integrity": "sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.36", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", - "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz", - "integrity": "sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw==", - "dev": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.44.3", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", - "integrity": "sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz", - "integrity": "sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", - "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.18", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz", - "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.37", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", - "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true - }, - "node_modules/@types/http-errors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", - "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", - "dev": true - }, - "node_modules/@types/http-proxy": { - "version": "1.17.12", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.12.tgz", - "integrity": "sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.6.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.5.tgz", - "integrity": "sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.8", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", - "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/binary-install": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.1.0.tgz", - "integrity": "sha512-rkwNGW+3aQVSZoD0/o3mfPN6Yxh3Id0R/xzTVBVVpGNlVz8EGwusksxRlbk/A5iKTZt9zkMn3qIqmAt3vpfbzg==", - "dev": true, - "dependencies": { - "axios": "^0.26.1", - "rimraf": "^3.0.2", - "tar": "^6.1.11" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", - "dev": true, - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.11", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz", - "integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001538", - "electron-to-chromium": "^1.4.526", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001538", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz", - "integrity": "sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clean-css": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", - "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dev": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.528", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz", - "integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", - "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", - "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", - "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", - "dev": true, - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "webpack": "^5.20.0" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", - "dev": true, - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", - "dev": true, - "dependencies": { - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy-transport/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/spdy/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/spdy/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz", - "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/text-encoding": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", - "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", - "deprecated": "no longer maintained", - "dev": true - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-loader": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", - "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/wasm-pack": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.12.1.tgz", - "integrity": "sha512-dIyKWUumPFsGohdndZjDXRFaokUT/kQS+SavbbiXVAvA/eN4riX5QNdB6AhXQx37zNxluxQkuixZUgJ8adKjOg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "binary-install": "^1.0.1" - }, - "bin": { - "wasm-pack": "run.js" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", - "dev": true, - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", - "dev": true, - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-merge": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", - "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } -} diff --git a/languages/js_webassembly/package.json b/languages/js_webassembly/package.json deleted file mode 100644 index df67eb9b7..000000000 --- a/languages/js_webassembly/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "scripts": { - "build": "wasm-pack build ../../crates/bitwarden-wasm --out-dir ../../languages/js_webassembly/pkg --dev", - "build:watch": "npm run build && webpack serve" - }, - "devDependencies": { - "html-webpack-plugin": "5.5.3", - "text-encoding": "0.7.0", - "ts-loader": "9.5.0", - "wasm-pack": "0.12.1", - "webpack": "5.88.2", - "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" - } -} diff --git a/languages/js_webassembly/styles.css b/languages/js_webassembly/styles.css deleted file mode 100644 index 4c336b64d..000000000 --- a/languages/js_webassembly/styles.css +++ /dev/null @@ -1,7 +0,0 @@ -html, -body { - height: 400px; - width: 200px; - margin: 0; - box-sizing: border-box; -} diff --git a/languages/js_webassembly/tsconfig.json b/languages/js_webassembly/tsconfig.json deleted file mode 100644 index ac51138c0..000000000 --- a/languages/js_webassembly/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist/", - "module": "es2020", - "target": "es5", - "jsx": "react", - "allowJs": true, - "moduleResolution": "node", - "sourceMap": true - } -} diff --git a/languages/js_webassembly/webpack.config.js b/languages/js_webassembly/webpack.config.js deleted file mode 100644 index 430df86f6..000000000 --- a/languages/js_webassembly/webpack.config.js +++ /dev/null @@ -1,57 +0,0 @@ -const path = require("path"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const webpack = require("webpack"); - -module.exports = { - entry: "./index.ts", - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - { - test: /\.wasm$/, - type: "webassembly/sync", - }, - ], - }, - resolve: { - extensions: [".tsx", ".ts", ".js"], - }, - output: { - path: path.resolve(__dirname, "dist"), - filename: "index.js", - }, - plugins: [ - new HtmlWebpackPlugin(), - // Have this example work in Edge which doesn't ship `TextEncoder` or - // `TextDecoder` at this time. - new webpack.ProvidePlugin({ - TextDecoder: ["text-encoding", "TextDecoder"], - TextEncoder: ["text-encoding", "TextEncoder"], - }), - ], - experiments: { - syncWebAssembly: true, - }, - devServer: { - proxy: { - "/api": { - target: "http://localhost:4000", - pathRewrite: { "^/api": "" }, - secure: false, - changeOrigin: true, - }, - "/identity": { - target: "http://localhost:33656", - pathRewrite: { "^/identity": "" }, - secure: false, - changeOrigin: true, - }, - }, - }, - mode: "development", - devtool: "source-map", -}; diff --git a/languages/kotlin/app/build.gradle b/languages/kotlin/app/build.gradle index 709e32889..ef42d43bb 100644 --- a/languages/kotlin/app/build.gradle +++ b/languages/kotlin/app/build.gradle @@ -60,6 +60,7 @@ dependencies { implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' + implementation "androidx.biometric:biometric:1.1.0" implementation "io.ktor:ktor-client-core:2.3.3" implementation "io.ktor:ktor-client-cio:2.3.3" implementation "io.ktor:ktor-client-content-negotiation:2.3.3" diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt new file mode 100644 index 000000000..8f8115e8d --- /dev/null +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/Biometrics.kt @@ -0,0 +1,132 @@ +package com.bitwarden.myapplication + +import android.os.Build +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Log +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators +import androidx.biometric.BiometricPrompt +import androidx.biometric.BiometricPrompt.CryptoObject +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import java.security.KeyStore +import java.util.Base64 +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.GCMParameterSpec + +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the biometric unlock functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +class Biometric(private var activity: FragmentActivity) { + private var promptInfo: BiometricPrompt.PromptInfo = + BiometricPrompt.PromptInfo.Builder().setTitle("Unlock") + .setSubtitle("Bitwarden biometric unlock") + .setDescription("Confirm biometric to continue").setConfirmationRequired(true) + .setNegativeButtonText("Use account password").build() + + suspend fun encryptString( + keyName: String, plaintext: String, callback: (String, String) -> Unit + ) { + if (canAuthenticate()) { + val cipher = getCipher() + cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(keyName)) + + val bio = createBiometricPrompt { + val ciphertext = it.cipher!!.doFinal(plaintext.toByteArray()) + callback( + String(Base64.getEncoder().encode(ciphertext)), + String(Base64.getEncoder().encode(cipher.iv)) + ) + } + CoroutineScope(Dispatchers.Main).async { + bio.authenticate(promptInfo, CryptoObject(cipher)) + }.await() + } + } + + suspend fun decryptString( + keyName: String, encrypted: String, initializationVector: String, callback: (String) -> Unit + ) { + if (canAuthenticate()) { + val enc = Base64.getDecoder().decode(encrypted) + val iv = Base64.getDecoder().decode(initializationVector) + + val cipher = getCipher() + cipher.init(Cipher.DECRYPT_MODE, getSecretKey(keyName), GCMParameterSpec(128, iv)) + + val bio = createBiometricPrompt { + callback(String(it.cipher!!.doFinal(enc))) + } + + CoroutineScope(Dispatchers.Main).async { + bio.authenticate(promptInfo, CryptoObject(cipher)) + }.await() + } + } + + private fun canAuthenticate() = BiometricManager.from(activity) + .canAuthenticate(Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS + + private fun createBiometricPrompt(processData: (CryptoObject) -> Unit): BiometricPrompt { + return BiometricPrompt(activity, + ContextCompat.getMainExecutor(activity), + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + Log.e("Biometric", "$errorCode :: $errString") + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + Log.e("Biometric", "Authentication failed for an unknown reason") + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + processData(result.cryptoObject!!) + } + }) + } + + private fun getCipher(): Cipher { + val transform = + "${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_GCM}/${KeyProperties.ENCRYPTION_PADDING_NONE}" + return Cipher.getInstance(transform) + } + + private fun getSecretKey(keyName: String): SecretKey { + // If the SecretKey exists, return it + val keyStore = KeyStore.getInstance("AndroidKeyStore") + keyStore.load(null) + keyStore.getKey(keyName, null)?.let { return it as SecretKey } + + // Otherwise, we generate a new one + val keyGenParams = KeyGenParameterSpec.Builder( + keyName, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ).apply { + setBlockModes(KeyProperties.BLOCK_MODE_GCM) + setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + setKeySize(256) + setUserAuthenticationRequired(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG) + } + }.build() + + val keyGenerator = + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") + keyGenerator.init(keyGenParams) + return keyGenerator.generateKey() + } +} diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index 2d204e075..f51d0309e 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -1,20 +1,35 @@ package com.bitwarden.myapplication +import android.content.Context import android.os.Bundle -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentActivity import com.bitwarden.core.DateTime import com.bitwarden.core.Folder -import com.bitwarden.core.InitCryptoRequest -import com.bitwarden.core.Kdf +import com.bitwarden.core.InitOrgCryptoRequest +import com.bitwarden.core.InitUserCryptoMethod +import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.Uuid +import com.bitwarden.crypto.HashPurpose +import com.bitwarden.crypto.Kdf import com.bitwarden.myapplication.ui.theme.MyApplicationTheme import com.bitwarden.sdk.Client import io.ktor.client.HttpClient @@ -42,144 +57,356 @@ import java.security.cert.X509Certificate import java.util.Base64 import javax.net.ssl.X509TrustManager -class MainActivity : ComponentActivity() { +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +const val SERVER_URL = "https://10.0.2.2:8080/" +const val API_URL = SERVER_URL + "api/" +const val IDENTITY_URL = SERVER_URL + "identity/" + +const val EMAIL = "test@bitwarden.com" +const val PASSWORD = "asdfasdfasdf" + +const val PIN = "1234" + +// We should separate keys for each user by appending the user_id +const val BIOMETRIC_KEY = "biometric_key" + +class MainActivity : FragmentActivity() { + private lateinit var biometric: Biometric + private lateinit var client: Client + private lateinit var http: HttpClient + + private var accessToken = "" + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + biometric = Biometric(this) + client = Client(null) + http = httpClient() - val SERVER_URL = "https://10.0.2.2:8080/" - val API_URL = SERVER_URL + "api/" - val IDENTITY_URL = SERVER_URL + "identity/" + setContent { + MyApplicationTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { - val EMAIL = "test@bitwarden.com" - val PASSWORD = "asdfasdfasdf" + val setupBiometrics = remember { mutableStateOf(true) } + val setupPin = remember { mutableStateOf(true) } + val outputText = remember { mutableStateOf("") } - GlobalScope.launch { - var client = Client(null) - val http = httpClient() - - ///////////////////////////// Get master password hash ///////////////////////////// - @Serializable - data class PreloginRequest(val email: String) - - @Serializable - data class PreloginResponse( - val kdf: UInt, - val kdfIterations: UInt, - val kdfMemory: UInt?, - val kdfParallelism: UInt? + Row { + Checkbox( + checked = setupBiometrics.value, + onCheckedChange = { isChecked -> + setupBiometrics.value = isChecked + }) + Text( + "Setup biometric unlock after login", + modifier = Modifier.align(CenterVertically) + ) + } + + Row { + Checkbox(checked = setupPin.value, onCheckedChange = { isChecked -> + setupPin.value = isChecked + }) + Text( + "Setup pin unlock after login", + modifier = Modifier.align(CenterVertically) + ) + } + + Button({ + GlobalScope.launch { + clientExamplePassword( + client, http, outputText, setupBiometrics.value, setupPin.value + ) + } + }) { + Text("Login with username + password") + } + + Divider( + color = Color.Black, + thickness = 1.dp, + modifier = Modifier.padding(30.dp) + ) + + Button({ + GlobalScope.launch { + clientExampleBiometrics(client, http, outputText) + } + }) { + Text("Unlock with biometrics") + } + + Button({ + GlobalScope.launch { + clientExamplePin(client, http, outputText) + } + }) { + Text("Unlock with PIN") + } + + Button({ + GlobalScope.launch { + client.destroy() + client = Client(null) + outputText.value = "OK" + } + }) { + Text("Lock & reset client") + } + + Text( + "Output: " + outputText.value, + modifier = Modifier.padding(vertical = 10.dp) + ) + } + } + } + } + } + + private suspend fun clientExamplePassword( + client: Client, + http: HttpClient, + outputText: MutableState, + setupBiometrics: Boolean, + setupPin: Boolean + ) { + println("### Logging in with username and password ###") + ///////////////////////////// Get master password hash ///////////////////////////// + @Serializable + data class PreloginRequest(val email: String) + + @Serializable + data class PreloginResponse( + val kdf: UInt, val kdfIterations: UInt, val kdfMemory: UInt?, val kdfParallelism: UInt? + ) + + val prelogin_body = http.post(IDENTITY_URL + "accounts/prelogin") { + contentType(ContentType.Application.Json) + setBody(PreloginRequest(EMAIL)) + }.body() + val kdf = if (prelogin_body.kdf == 0u) { + Kdf.Pbkdf2(prelogin_body.kdfIterations) + } else { + Kdf.Argon2id( + prelogin_body.kdfIterations, + prelogin_body.kdfMemory!!, + prelogin_body.kdfParallelism!! ) + } + val masterPasswordHash = + client.auth().hashPassword(EMAIL, PASSWORD, kdf, HashPurpose.SERVER_AUTHORIZATION) - val prelogin_body = http.post(IDENTITY_URL + "accounts/prelogin") { - contentType(ContentType.Application.Json) - setBody(PreloginRequest(EMAIL)) - }.body() - val kdf = if (prelogin_body.kdf == 0u) { - Kdf.Pbkdf2(prelogin_body.kdfIterations) - } else { - Kdf.Argon2id( - prelogin_body.kdfIterations, - prelogin_body.kdfMemory!!, - prelogin_body.kdfParallelism!! + ///////////////////////////// Login ///////////////////////////// + + @Serializable + data class LoginResponse( + val Key: String, + val PrivateKey: String, + val access_token: String, + val refresh_token: String, + ) + + val loginBody = http.post(IDENTITY_URL + "connect/token") { + contentType(ContentType.Application.Json) + header("Auth-Email", Base64.getEncoder().encodeToString(EMAIL.toByteArray())) + setBody(FormDataContent(Parameters.build { + append("scope", "api offline_access") + append("client_id", "web") + append("deviceType", "12") + append("deviceIdentifier", "0745d426-8dab-484a-9816-4959721d77c7") + append("deviceName", "edge") + + append("grant_type", "password") + append("username", EMAIL) + append("password", masterPasswordHash) + })) + }.body() + + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = loginBody.PrivateKey, + method = InitUserCryptoMethod.Password( + password = PASSWORD, userKey = loginBody.Key ) + ) + ) + + accessToken = loginBody.access_token + + decryptVault(client, http, outputText) + + if (setupBiometrics) { + // Save values for future logins + val sharedPref = getPreferences(Context.MODE_PRIVATE) + with(sharedPref.edit()) { + putString("accessToken", accessToken) + putString("privateKey", loginBody.PrivateKey) + + putInt("kdfType", prelogin_body.kdf.toInt()) + putInt("kdfIterations", prelogin_body.kdfIterations.toInt()) + putInt("kdfMemory", (prelogin_body.kdfMemory ?: 0u).toInt()) + putInt("kdfParallelism", (prelogin_body.kdfParallelism ?: 0u).toInt()) + + // TODO: This should be protected by Android's secure KeyStore + val decryptedKey = client.crypto().getUserEncryptionKey() + + biometric.encryptString(BIOMETRIC_KEY, decryptedKey) { key, iv -> + putString("encryptedUserKey", key) + putString("encryptedUserKeyIv", iv) + apply() + } + } + } + + if (setupPin) { + val pinOptions = client.crypto().derivePinKey(PIN); + + val sharedPref = getPreferences(Context.MODE_PRIVATE) + with(sharedPref.edit()) { + putString("accessToken", accessToken) + putString("privateKey", loginBody.PrivateKey) + + putInt("kdfType", prelogin_body.kdf.toInt()) + putInt("kdfIterations", prelogin_body.kdfIterations.toInt()) + putInt("kdfMemory", (prelogin_body.kdfMemory ?: 0u).toInt()) + putInt("kdfParallelism", (prelogin_body.kdfParallelism ?: 0u).toInt()) + + putString("encryptedPin", pinOptions.encryptedPin) + putString("pinProtectedUserKey", pinOptions.pinProtectedUserKey) + apply() } - val masterPasswordHash = client.auth().hashPassword(EMAIL, PASSWORD, kdf) + } + } - ///////////////////////////// Login ///////////////////////////// + private suspend fun clientExampleBiometrics( + client: Client, http: HttpClient, outputText: MutableState + ) { + println("### Unlocking with Biometrics ###") - @Serializable - data class LoginResponse( - val Key: String, - val PrivateKey: String, - val access_token: String, - val refresh_token: String, + val pref = getPreferences(Context.MODE_PRIVATE) + accessToken = pref.getString("accessToken", "").orEmpty() + val privateKey = pref.getString("privateKey", "") + + val kdf = if (pref.getInt("kdfType", 0) == 0) { + Kdf.Pbkdf2(pref.getInt("kdfIterations", 0).toUInt()) + } else { + Kdf.Argon2id( + pref.getInt("kdfIterations", 0).toUInt(), + pref.getInt("kdfMemory", 0).toUInt(), + pref.getInt("kdfParallelism", 0).toUInt() ) + } + + val encryptedUserKey = pref.getString("encryptedUserKey", "")!! + val encryptedUserKeyIv = pref.getString("encryptedUserKeyIv", "")!! - val loginBody = http.post(IDENTITY_URL + "connect/token") { - contentType(ContentType.Application.Json) - header("Auth-Email", Base64.getEncoder().encodeToString(EMAIL.toByteArray())) - setBody(FormDataContent(Parameters.build { - append("scope", "api offline_access") - append("client_id", "web") - append("deviceType", "12") - append("deviceIdentifier", "0745d426-8dab-484a-9816-4959721d77c7") - append("deviceName", "edge") - - append("grant_type", "password") - append("username", EMAIL) - append("password", masterPasswordHash) - })) - }.body() - - ///////////////////////////// Sync ///////////////////////////// - - val syncBody = http.get(API_URL + "sync?excludeDomains=true") { - bearerAuth(loginBody.access_token) - }.body() - - val folders = (syncBody["folders"] as JsonArray).map { - val o = it as JsonObject; - Folder( - (o["id"] as JsonPrimitive).content, - (o["name"] as JsonPrimitive).content, - DateTime.parse( - (o["revisionDate"] as JsonPrimitive).content + biometric.decryptString( + BIOMETRIC_KEY, encryptedUserKey, encryptedUserKeyIv + ) { key -> + GlobalScope.launch { + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( + kdfParams = kdf, + email = EMAIL, + privateKey = privateKey!!, + method = InitUserCryptoMethod.DecryptedKey(decryptedUserKey = key) ) ) + + decryptVault(client, http, outputText) } + } + } - ///////////////////////////// Initialize crypto ///////////////////////////// - val orgs = ((syncBody["profile"] as JsonObject)["organizations"]) as JsonArray - val orgKeys = HashMap() + private suspend fun clientExamplePin( + client: Client, http: HttpClient, outputText: MutableState + ) { + println("### Unlocking with PIN ###") - for (org in orgs) { - val o = org as JsonObject - orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content - } + val pref = getPreferences(Context.MODE_PRIVATE) + accessToken = pref.getString("accessToken", "").orEmpty() + val privateKey = pref.getString("privateKey", "") + + val kdf = if (pref.getInt("kdfType", 0) == 0) { + Kdf.Pbkdf2(pref.getInt("kdfIterations", 0).toUInt()) + } else { + Kdf.Argon2id( + pref.getInt("kdfIterations", 0).toUInt(), + pref.getInt("kdfMemory", 0).toUInt(), + pref.getInt("kdfParallelism", 0).toUInt() + ) + } - client.crypto().initializeCrypto( - InitCryptoRequest( + val encryptedPin = pref.getString("encryptedPin", "")!! + val pinProtectedUserKey = pref.getString("pinProtectedUserKey", "")!! + + GlobalScope.launch { + client.crypto().initializeUserCrypto( + InitUserCryptoRequest( kdfParams = kdf, email = EMAIL, - password = PASSWORD, - userKey = loginBody.Key, - privateKey = loginBody.PrivateKey, - organizationKeys = orgKeys + privateKey = privateKey!!, + method = InitUserCryptoMethod.Pin( + pinProtectedUserKey = pinProtectedUserKey, pin = PIN + ) ) ) - ///////////////////////////// Decrypt some folders ///////////////////////////// + decryptVault(client, http, outputText) + } + } - val decryptedFolders = client.vault().folders().decryptList(folders) + suspend fun decryptVault(client: Client, http: HttpClient, outputText: MutableState) { + ///////////////////////////// Sync ///////////////////////////// - println(decryptedFolders) + val syncBody = http.get(API_URL + "sync?excludeDomains=true") { + bearerAuth(accessToken) + }.body() + val folders = (syncBody["folders"] as JsonArray).map { + val o = it as JsonObject + Folder( + (o["id"] as JsonPrimitive).content, + (o["name"] as JsonPrimitive).content, + DateTime.parse( + (o["revisionDate"] as JsonPrimitive).content + ) + ) } - setContent { - MyApplicationTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background - ) { - Greeting("Hey") - } - } + ///////////////////////////// Initialize org crypto ///////////////////////////// + val orgs = ((syncBody["profile"] as JsonObject)["organizations"]) as JsonArray + val orgKeys = HashMap() + + for (org in orgs) { + val o = org as JsonObject + orgKeys[(o["id"] as JsonPrimitive).content] = (o["key"] as JsonPrimitive).content } - } -} -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", modifier = modifier - ) -} + client.crypto().initializeOrgCrypto(InitOrgCryptoRequest(organizationKeys = orgKeys)) + + ///////////////////////////// Decrypt some folders ///////////////////////////// -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - MyApplicationTheme { - Greeting("Sdk") + val decryptedFolders = client.vault().folders().decryptList(folders) + outputText.value = decryptedFolders.toString() + println(decryptedFolders) } } diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md deleted file mode 100644 index 9e8c19929..000000000 --- a/languages/kotlin/doc.md +++ /dev/null @@ -1,1255 +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. - -## Client - -### `new` - -Initialize a new instance of the SDK client - -**Arguments**: - -- settings: Option - -**Output**: Arc - -### `crypto` - -Crypto operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `vault` - -Vault item operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `generators` - -Generator operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `auth` - -Auth operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `echo` - -Test method, echoes back the input - -**Arguments**: - -- self: -- msg: String - -**Output**: String - -## ClientAuth - -### `password_strength` - -**API Draft:** Calculate Password Strength - -**Arguments**: - -- self: -- password: String -- email: String -- additional_inputs: Vec - -**Output**: - -### `satisfies_policy` - -**API Draft:** Evaluate if the provided password satisfies the provided policy - -**Arguments**: - -- self: -- password: String -- strength: -- policy: [MasterPasswordPolicyOptions](#masterpasswordpolicyoptions) - -**Output**: - -### `hash_password` - -Hash the user password - -**Arguments**: - -- self: -- email: String -- password: String -- kdf_params: [Kdf](#kdf) - -**Output**: std::result::Result - -### `make_register_keys` - -Generate keys needed for registration process - -**Arguments**: - -- self: -- email: String -- password: String -- kdf: [Kdf](#kdf) - -**Output**: std::result::Result - -## ClientCiphers - -### `encrypt` - -Encrypt cipher - -**Arguments**: - -- self: -- cipher_view: [CipherView](#cipherview) - -**Output**: std::result::Result - -### `decrypt` - -Decrypt cipher - -**Arguments**: - -- self: -- cipher: [Cipher](#cipher) - -**Output**: std::result::Result - -### `decrypt_list` - -Decrypt cipher list - -**Arguments**: - -- self: -- ciphers: Vec - -**Output**: std::result::Result - -## ClientCollections - -### `decrypt` - -Decrypt collection - -**Arguments**: - -- self: -- collection: [Collection](#collection) - -**Output**: std::result::Result - -### `decrypt_list` - -Decrypt collection list - -**Arguments**: - -- self: -- collections: Vec - -**Output**: std::result::Result - -## ClientCrypto - -### `initialize_crypto` - -Initialization method for the crypto. Needs to be called before any other crypto operations. - -**Arguments**: - -- self: -- req: [InitCryptoRequest](#initcryptorequest) - -**Output**: std::result::Result<,BitwardenError> - -## ClientExporters - -### `export_vault` - -**API Draft:** Export user vault - -**Arguments**: - -- self: -- folders: Vec -- ciphers: Vec -- format: [ExportFormat](#exportformat) - -**Output**: std::result::Result - -### `export_organization_vault` - -**API Draft:** Export organization vault - -**Arguments**: - -- self: -- collections: Vec -- ciphers: Vec -- format: [ExportFormat](#exportformat) - -**Output**: std::result::Result - -## ClientFolders - -### `encrypt` - -Encrypt folder - -**Arguments**: - -- self: -- folder: [FolderView](#folderview) - -**Output**: std::result::Result - -### `decrypt` - -Decrypt folder - -**Arguments**: - -- self: -- folder: [Folder](#folder) - -**Output**: std::result::Result - -### `decrypt_list` - -Decrypt folder list - -**Arguments**: - -- self: -- folders: Vec - -**Output**: std::result::Result - -## ClientGenerators - -### `password` - -**API Draft:** Generate Password - -**Arguments**: - -- self: -- settings: [PasswordGeneratorRequest](#passwordgeneratorrequest) - -**Output**: std::result::Result - -### `passphrase` - -**API Draft:** Generate Passphrase - -**Arguments**: - -- self: -- settings: [PassphraseGeneratorRequest](#passphrasegeneratorrequest) - -**Output**: std::result::Result - -## ClientPasswordHistory - -### `encrypt` - -Encrypt password history - -**Arguments**: - -- self: -- password_history: [PasswordHistoryView](#passwordhistoryview) - -**Output**: std::result::Result - -### `decrypt_list` - -Decrypt password history - -**Arguments**: - -- self: -- list: Vec - -**Output**: std::result::Result - -## ClientSends - -### `encrypt` - -Encrypt send - -**Arguments**: - -- self: -- send: [SendView](#sendview) - -**Output**: std::result::Result - -### `encrypt_buffer` - -Encrypt a send file in memory - -**Arguments**: - -- self: -- send: [Send](#send) -- buffer: Vec<> - -**Output**: std::result::Result - -### `encrypt_file` - -Encrypt a send file located in the file system - -**Arguments**: - -- self: -- send: [Send](#send) -- decrypted_file_path: String -- encrypted_file_path: String - -**Output**: std::result::Result<,BitwardenError> - -### `decrypt` - -Decrypt send - -**Arguments**: - -- self: -- send: [Send](#send) - -**Output**: std::result::Result - -### `decrypt_list` - -Decrypt send list - -**Arguments**: - -- self: -- sends: Vec - -**Output**: std::result::Result - -### `decrypt_buffer` - -Decrypt a send file in memory - -**Arguments**: - -- self: -- send: [Send](#send) -- buffer: Vec<> - -**Output**: std::result::Result - -### `decrypt_file` - -Decrypt a send file located in the file system - -**Arguments**: - -- self: -- send: [Send](#send) -- encrypted_file_path: String -- decrypted_file_path: String - -**Output**: std::result::Result<,BitwardenError> - -## ClientVault - -### `folders` - -Folder operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `collections` - -Collections operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `ciphers` - -Ciphers operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `password_history` - -Password history operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -### `sends` - -Sends operations - -**Arguments**: - -- self: Arc - -**Output**: Arc - -# References - -References are generated from the JSON schemas and should mostly match the kotlin and swift -implementations. - -## `Cipher` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
idstring,null
organizationIdstring,null
folderIdstring,null
collectionIdsarray
name
notes
type
login
identity
card
secureNote
favoriteboolean
reprompt
organizationUseTotpboolean
editboolean
viewPasswordboolean
localData
attachmentsarray
fieldsarray
passwordHistoryarray
creationDatestring
deletedDatestring,null
revisionDatestring
- -## `CipherView` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
idstring,null
organizationIdstring,null
folderIdstring,null
collectionIdsarray
namestring
notesstring
type
login
identity
card
secureNote
favoriteboolean
reprompt
organizationUseTotpboolean
editboolean
viewPasswordboolean
localData
attachmentsarray
fieldsarray
passwordHistoryarray
creationDatestring
deletedDatestring,null
revisionDatestring
- -## `Collection` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
idstring
organizationIdstring
name
externalIdstring,null
hidePasswordsboolean
readOnlyboolean
- -## `ExportFormat` - - - - - - - - - - - - - - - -
KeyTypeDescription
EncryptedJsonobject
- - - - - - - - - - - -
KeyTypeDescription
passwordstring
-
- -## `Folder` - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
idstring
name
revisionDatestring
- -## `FolderView` - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
idstring
namestring
revisionDatestring
- -## `InitCryptoRequest` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
kdfParamsThe user's KDF parameters, as received from the prelogin request
emailstringThe user's email address
passwordstringThe user's master password
userKeystringThe user's encrypted symmetric crypto key
privateKeystringThe user's encryptred private key
organizationKeysobjectThe encryption keys for all the organizations the user is a part of
- -## `Kdf` - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
pBKDF2object
- - - - - - - - - - - -
KeyTypeDescription
iterationsinteger
-
argon2idobject
- - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
iterationsinteger
memoryinteger
parallelisminteger
-
- -## `MasterPasswordPolicyOptions` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
min_complexityinteger
min_lengthinteger
require_upperboolean
require_lowerboolean
require_numbersboolean
require_specialboolean
enforce_on_loginbooleanFlag to indicate if the policy should be enforced on login. If true, and the user's password does not meet the policy requirements, the user will be forced to update their password.
- -## `PassphraseGeneratorRequest` - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
numWordsinteger,null
wordSeparatorstring,null
capitalizeboolean,null
includeNumberboolean,null
- -## `PasswordGeneratorRequest` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
lowercaseboolean
uppercaseboolean
numbersboolean
specialboolean
lengthinteger,null
avoidAmbiguousboolean,null
minLowercaseboolean,null
minUppercaseboolean,null
minNumberboolean,null
minSpecialboolean,null
- -## `PasswordHistoryView` - - - - - - - - - - - - - - - - - -
KeyTypeDescription
passwordstring
lastUsedDatestring
- -## `Send` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
idstring
accessIdstring
name
notes
key
passwordstring,null
type
file
text
maxAccessCountinteger,null
accessCountinteger
disabledboolean
hideEmailboolean
revisionDatestring
deletionDatestring
expirationDatestring,null
- -## `SendView` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
idstring
accessIdstring
namestring
notesstring,null
key
passwordstring,null
type
file
text
maxAccessCountinteger,null
accessCountinteger
disabledboolean
hideEmailboolean
revisionDatestring
deletionDatestring
expirationDatestring,null
diff --git a/languages/kotlin/sdk/build.gradle b/languages/kotlin/sdk/build.gradle index 3fb6956e2..d92dad56a 100644 --- a/languages/kotlin/sdk/build.gradle +++ b/languages/kotlin/sdk/build.gradle @@ -30,6 +30,10 @@ android { jvmTarget = '1.8' } + lint { + baseline = file("lint-baseline.xml") + } + publishing { singleVariant('release') { withSourcesJar() @@ -46,12 +50,12 @@ publishing { // Determine the version from the git history. // // PRs: use the branch name. - // Master: Grab it from `crates/bitwarden/Cargo.toml` + // Main: Grab it from `crates/bitwarden/Cargo.toml` def branchName = "git branch --show-current".execute().text.trim() - if (branchName == "master") { - def content = ['grep', '-o', '^version = ".*"', '../../crates/bitwarden/Cargo.toml'].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() diff --git a/languages/kotlin/sdk/lint-baseline.xml b/languages/kotlin/sdk/lint-baseline.xml new file mode 100644 index 000000000..3ae6c45f5 --- /dev/null +++ b/languages/kotlin/sdk/lint-baseline.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/languages/php/.gitignore b/languages/php/.gitignore new file mode 100644 index 000000000..b2a69e9a0 --- /dev/null +++ b/languages/php/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +vendor diff --git a/languages/php/README.md b/languages/php/README.md new file mode 100644 index 000000000..9e4a9385d --- /dev/null +++ b/languages/php/README.md @@ -0,0 +1,100 @@ +# Bitwarden Secrets Manager SDK wrapper for PHP + +PHP bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some functionality. +Supported are CRUD operations on project and secret entities. + +## Installation + +Requirements: +- PHP >= 8.0 +- Composer +- Bitwarden C libraries which you can generate using BitwardenSDK and following instructions in its readme (requires Rust). https://github.com/bitwarden/sdk +If you are not using the standalone version of this library, file will be placed in `target/debug` folder if you are using from BitwardenSDK repository. +- Access token for the Bitwarden account + + +## Usage + +To interact with the client first you need to obtain the access token from Bitwarden. +You can then initialize BitwardenSettings passing $api_url and $identity_url if needed. These parameteres are +optional and if they are not defined, BitwardenSettings instance will try to get these values from ENV, and +if they are not defined there as well, it will use defaults: `https://api.bitwarden.com` as api_url and +`https://identity.bitwarden.com` as identity_url. You can also pass device type as argument but that is entirely +optional. + +Passing BitwardenSettings instance to BitwardenClient will initialize it. Before using the client you must +be authorized by calling the access_token_login method passing your Bitwarden access token to it. + + +```php +$access_token = ''; +$api_url = ""; +$identity_url = ""; +$bitwarden_settings = new \Bitwarden\Sdk\BitwardenSettings($api_url, $identity_url); + +$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($bitwarden_settings); +$bitwarden_client->access_token_login($access_token); +``` + +After successful authorization you can interact with client to manage your projects and secrets. +```php +$organization_id = ""; + +$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($bitwarden_settings); +$res = $bitwarden_client->access_token_login($access_token); + +// create project +$name = "PHP project" +$res = $bitwarden_client->projects->create($name, $organization_id); +$project_id = $res->id; + +// get project +$res = $bitwarden_client->projects->get($project_id); + +// list projects +$res = $bitwarden_client->projects->list($organization_id); + +// update project +$name = "Updated PHP project" +$res = $bitwarden_client->projects->put($project_id, $name, $organization_id); + +// get secret +$res = $bitwarden_client->secrets->get($secret_id); + +// list secrets +$res = $bitwarden_client->secrets->list($organization_id); + +// delete project +$res = $bitwarden_client->projects->delete([$project_id]); + +``` + +Similarly, you interact with secrets: +```php +$organization_id = ""; + +// create secret +$key = "AWS secret key"; +$note = "Private account"; +$secret = "76asaj,Is_)" +$res = $bitwarden_client->secrets->create($key, $note, $organization_id, [$project_id], $secret); +$secret_id = $res->id; + +// get secret +$res = $bitwarden_sdk->secrets->get($secret_id); + +// list secrets +$res = $bitwarden_client->secrets->list($organization_id); + +// update secret +$note = "Updated account"; +$key = "AWS private updated" +$secret = "7uYTE,:Aer" +$res = $bitwarden_client->secrets->update($secret_id, $key, $note, $organization_id, [$project_id], $secret); + +// delete secret +$res = $bitwarden_sdk->secrets->delete([$secret_id]); +``` + + +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/php/composer.json b/languages/php/composer.json new file mode 100644 index 000000000..85447e72a --- /dev/null +++ b/languages/php/composer.json @@ -0,0 +1,23 @@ +{ + "name": "bitwarden/sdk-secrets", + "description": "PHP bindings for interacting with the Bitwarden Secrets Manager. This is a beta release and might be missing some functionality.", + "type": "library", + "keywords": ["bitwarden","sdk","password-manager"], + "homepage": "https://github.com/bitwarden/sdk", + "version": "0.1.0", + "require": { + "php": "^8.0", + "swaggest/json-schema": "^0.12.42", + "ext-ffi": "*" + }, + "autoload": { + "psr-4": { + "Bitwarden\\Sdk\\": "src/" + } + }, + "authors": [ + { + "name": "Bitwarden Inc." + } + ] +} diff --git a/languages/php/composer.lock b/languages/php/composer.lock new file mode 100644 index 000000000..fc6b42c4f --- /dev/null +++ b/languages/php/composer.lock @@ -0,0 +1,247 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7081b1bfe099982a63ad06d5ab9fa66d", + "packages": [ + { + "name": "phplang/scope-exit", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/phplang/scope-exit.git", + "reference": "239b73abe89f9414aa85a7ca075ec9445629192b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phplang/scope-exit/zipball/239b73abe89f9414aa85a7ca075ec9445629192b", + "reference": "239b73abe89f9414aa85a7ca075ec9445629192b", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpLang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Sara Golemon", + "email": "pollita@php.net", + "homepage": "https://twitter.com/SaraMG", + "role": "Developer" + } + ], + "description": "Emulation of SCOPE_EXIT construct from C++", + "homepage": "https://github.com/phplang/scope-exit", + "keywords": [ + "cleanup", + "exit", + "scope" + ], + "support": { + "issues": "https://github.com/phplang/scope-exit/issues", + "source": "https://github.com/phplang/scope-exit/tree/master" + }, + "time": "2016-09-17T00:15:18+00:00" + }, + { + "name": "swaggest/json-diff", + "version": "v3.10.4", + "source": { + "type": "git", + "url": "https://github.com/swaggest/json-diff.git", + "reference": "f4e511708060ff7511a3743fab4aa484a062bcfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swaggest/json-diff/zipball/f4e511708060ff7511a3743fab4aa484a062bcfb", + "reference": "f4e511708060ff7511a3743fab4aa484a062bcfb", + "shasum": "" + }, + "require": { + "ext-json": "*" + }, + "require-dev": { + "phperf/phpunit": "4.8.37" + }, + "type": "library", + "autoload": { + "psr-4": { + "Swaggest\\JsonDiff\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Viacheslav Poturaev", + "email": "vearutop@gmail.com" + } + ], + "description": "JSON diff/rearrange/patch/pointer library for PHP", + "support": { + "issues": "https://github.com/swaggest/json-diff/issues", + "source": "https://github.com/swaggest/json-diff/tree/v3.10.4" + }, + "time": "2022-11-09T13:21:05+00:00" + }, + { + "name": "swaggest/json-schema", + "version": "v0.12.42", + "source": { + "type": "git", + "url": "https://github.com/swaggest/php-json-schema.git", + "reference": "d23adb53808b8e2da36f75bc0188546e4cbe3b45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swaggest/php-json-schema/zipball/d23adb53808b8e2da36f75bc0188546e4cbe3b45", + "reference": "d23adb53808b8e2da36f75bc0188546e4cbe3b45", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.4", + "phplang/scope-exit": "^1.0", + "swaggest/json-diff": "^3.8.2", + "symfony/polyfill-mbstring": "^1.19" + }, + "require-dev": { + "phperf/phpunit": "4.8.37" + }, + "suggest": { + "ext-mbstring": "For better performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "Swaggest\\JsonSchema\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Viacheslav Poturaev", + "email": "vearutop@gmail.com" + } + ], + "description": "High definition PHP structures with JSON-schema based validation", + "support": { + "email": "vearutop@gmail.com", + "issues": "https://github.com/swaggest/php-json-schema/issues", + "source": "https://github.com/swaggest/php-json-schema/tree/v0.12.42" + }, + "time": "2023-09-12T14:43:42+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.0", + "ext-ffi": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/languages/php/example.php b/languages/php/example.php new file mode 100644 index 000000000..7eafcb96a --- /dev/null +++ b/languages/php/example.php @@ -0,0 +1,47 @@ +access_token_login($access_token); + +// create project +$res = $bitwarden_client->projects->create('php project', $organization_id); +$project_id = $res->id; + +// get project +$res = $bitwarden_client->projects->get($project_id); + +// list projects +$res = $bitwarden_client->projects->list($organization_id); + +// update project +$res = $bitwarden_client->projects->put($project_id, 'php test awesome', $organization_id); + +// create secret +$res = $bitwarden_client->secrets->create("New Key", "hello world", $organization_id, [$project_id], "123"); +$secret_id = $res->id; + +// get secret +$res = $bitwarden_client->secrets->get($secret_id); + +// list secrets +$res = $bitwarden_client->secrets->list($organization_id); + +// update secret +$res = $bitwarden_client->secrets->update($secret_id, "hello world 2", "hello", $organization_id, [$project_id], "123"); + +// delete secret +$res = $bitwarden_client->secrets->delete([$secret_id]); + +// delete project +$res = $bitwarden_client->projects->delete([$project_id]); diff --git a/languages/php/src/BitwardenClient.php b/languages/php/src/BitwardenClient.php new file mode 100644 index 000000000..79fccdf9c --- /dev/null +++ b/languages/php/src/BitwardenClient.php @@ -0,0 +1,64 @@ +clientSettings = new ClientSettings(); + $this->clientSettings->apiUrl = $bitwardenSettings->get_api_url(); + $this->clientSettings->identityUrl = $bitwardenSettings->get_identity_url(); + $this->clientSettings->userAgent = "Bitwarden PHP-SDK"; + + $this->bitwarden_lib = new BitwardenLib(); + $this->handle = $this->bitwarden_lib->init($this->clientSettings); + + $this->commandRunner = new CommandRunner($this->bitwarden_lib, $this->handle); + $this->projects = new ProjectsClient($this->commandRunner); + $this->secrets = new SecretsClient($this->commandRunner); + } + + /** + * @throws \Exception + */ + public function access_token_login(string $access_token) + { + $access_token_request = new AccessTokenLoginRequest(); + $access_token_request->accessToken = $access_token; + $command = new Command(); + $command->accessTokenLogin = $access_token_request->jsonSerialize(); + $result = $this->commandRunner->run($command); + if (!isset($result->authenticated)) { + throw new \Exception("Authorization error"); + } + + if ($result->authenticated == False) { + throw new \Exception("Unauthorized"); + } + } + + public function __destruct() + { + $this->bitwarden_lib->free_mem(); + } +} diff --git a/languages/php/src/BitwardenLib.php b/languages/php/src/BitwardenLib.php new file mode 100644 index 000000000..351728986 --- /dev/null +++ b/languages/php/src/BitwardenLib.php @@ -0,0 +1,79 @@ +ffi = FFI::cdef(' + void* init(const char* param); + char* run_command(void* c_str_ptr, void* client_ptr); + void free_mem(void* client_ptr);', + $lib_file + ); + } + + public function init(ClientSettings $client_settings): FFI\CData + { + $this->handle = $this->ffi->init(json_encode($client_settings->jsonSerialize())); + return $this->handle; + } + + public function run_command(Command $command): \stdClass + { + $encoded_json = json_encode($command->jsonSerialize()); + try { + $result = $this->ffi->run_command($encoded_json, $this->handle); + return json_decode(FFI::string($result)); + } catch (\FFI\Exception $e) { + throw new \RuntimeException('Error occurred during FFI operation: ' . $e->getMessage()); + } + } + + public function free_mem(): void + { + $this->ffi->free_mem($this->handle); + } +} diff --git a/languages/php/src/BitwardenSettings.php b/languages/php/src/BitwardenSettings.php new file mode 100644 index 000000000..b3d62bc2e --- /dev/null +++ b/languages/php/src/BitwardenSettings.php @@ -0,0 +1,26 @@ +api_url = $api_url; + $this->identity_url = $identity_url; + } + + public function get_api_url(): ?string + { + return $this->api_url; + } + + public function get_identity_url(): ?string + { + return $this->identity_url; + } +} diff --git a/languages/php/src/CommandRunner.php b/languages/php/src/CommandRunner.php new file mode 100644 index 000000000..9eec68b2d --- /dev/null +++ b/languages/php/src/CommandRunner.php @@ -0,0 +1,37 @@ +bitwardenLib = $bitwardenLib; + $this->handle = $handle; + } + + /** + * @throws \Exception + */ + public function run(Command $command): \stdClass + { + $result = $this->bitwardenLib->run_command($command); + if ($result->success == true) { + return $result->data; + } + + if (isset($result->errorMessage)) + { + throw new \Exception($result->errorMessage); + } + throw new \Exception("Unknown error occurred"); + } +} diff --git a/languages/php/src/ProjectsClient.php b/languages/php/src/ProjectsClient.php new file mode 100644 index 000000000..6b6f9fb6a --- /dev/null +++ b/languages/php/src/ProjectsClient.php @@ -0,0 +1,81 @@ +commandRunner = $commandRunner; + } + + public function get(string $project_id): \stdClass + { + $project_get_request = new ProjectGetRequest(); + $project_get_request->id = $project_id; + $project_get_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->get = $project_get_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function list(string $organization_id): \stdClass + { + $project_list_request = new ProjectsListRequest(); + $project_list_request->organizationId = $organization_id; + $project_list_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->list = $project_list_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function create(string $project_name, string $organization_id): \stdClass + { + $project_create_request = new ProjectCreateRequest(); + $project_create_request->name = $project_name; + $project_create_request->organizationId = $organization_id; + $project_create_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->create = $project_create_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function put(string $project_id, string $project_name, string $organization_id): \stdClass + { + $project_put_request = new ProjectPutRequest(); + $project_put_request->organizationId = $organization_id; + $project_put_request->name = $project_name; + $project_put_request->id = $project_id; + $project_put_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->update = $project_put_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function delete(array $ids): \stdClass + { + $projects_delete_request = new ProjectsDeleteRequest(); + $projects_delete_request->ids = $ids; + $projects_delete_request->validate(); + $project_command = new ProjectsCommand(); + $project_command->delete = $projects_delete_request->jsonSerialize(); + return $this->run_project_command($project_command); + } + + public function run_project_command($projectCommand): \stdClass + { + $command = new Command(); + $command->projects = $projectCommand; + return $this->commandRunner->run($command); + } +} diff --git a/languages/php/src/SecretsClient.php b/languages/php/src/SecretsClient.php new file mode 100644 index 000000000..d5c0b0cef --- /dev/null +++ b/languages/php/src/SecretsClient.php @@ -0,0 +1,98 @@ +commandRunner = $commandRunner; + } + + public function get(string $secret_id): \stdClass + { + $secret_get_request = new SecretGetRequest(); + $secret_get_request->id = $secret_id; + $secret_get_request->validate(); + $secret_command = new SecretsCommand(); + $secret_command->get = $secret_get_request->jsonSerialize(); + return $this->run_secret_command($secret_command); + } + + public function get_by_ids(array $secret_ids): \stdClass + { + $project_get_by_ids_request = new SecretsGetRequest(); + $project_get_by_ids_request->ids = $secret_ids; + $project_get_by_ids_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->get_by_ids = $project_get_by_ids_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function list(string $organization_id): \stdClass + { + $secrets_list_request = new SecretIdentifiersRequest(); + $secrets_list_request->organizationId = $organization_id; + $secrets_list_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->list = $secrets_list_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function create(string $key, string $note, string $organization_id, array $project_ids, string $value): \stdClass + { + $secrets_create_request = new SecretCreateRequest(); + $secrets_create_request->organizationId = $organization_id; + $secrets_create_request->projectIds = $project_ids; + $secrets_create_request->key = $key; + $secrets_create_request->note = $note; + $secrets_create_request->value = $value; + $secrets_create_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->create = $secrets_create_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function update(string $id, string $key, string $note, string $organization_id, array $project_ids, string $value): \stdClass + { + $secrets_put_request = new SecretPutRequest(); + $secrets_put_request->id = $id; + $secrets_put_request->organizationId = $organization_id; + $secrets_put_request->projectIds = $project_ids; + $secrets_put_request->key = $key; + $secrets_put_request->note = $note; + $secrets_put_request->value = $value; + $secrets_put_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->update = $secrets_put_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function delete(array $secrets_ids): \stdClass + { + $secrets_delete_request = new SecretsDeleteRequest(); + $secrets_delete_request->ids = $secrets_ids; + $secrets_delete_request->validate(); + $secrets_command = new SecretsCommand(); + $secrets_command->delete = $secrets_delete_request->jsonSerialize(); + return $this->run_secret_command($secrets_command); + } + + public function run_secret_command($secretsCommand): \stdClass + { + $command = new Command(); + $command->secrets = $secretsCommand; + return $this->commandRunner->run($command); + } +} diff --git a/languages/php/src/schemas/AccessTokenLoginRequest.php b/languages/php/src/schemas/AccessTokenLoginRequest.php new file mode 100644 index 000000000..a08805f92 --- /dev/null +++ b/languages/php/src/schemas/AccessTokenLoginRequest.php @@ -0,0 +1,39 @@ +accessToken = Schema::string(); + $properties->accessToken->description = "Bitwarden service API access token"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->description = "Login to Bitwarden with access token"; + $ownerSchema->required = array( + self::names()->accessToken, + ); + $ownerSchema->setFromRef('#/definitions/AccessTokenLoginRequest'); + } +} diff --git a/languages/php/src/schemas/BitwardenClassStructure.php b/languages/php/src/schemas/BitwardenClassStructure.php new file mode 100644 index 000000000..fd50354d4 --- /dev/null +++ b/languages/php/src/schemas/BitwardenClassStructure.php @@ -0,0 +1,11 @@ +properties = $properties; + $schema->objectItemClass = $className; + $schemaWrapper = new Wrapper($schema); + static::setUpProperties($properties, $schema); + if (null === $schema->getFromRefs()) { + $schema->setFromRef('#/definitions/' . $className); + } + if ($properties->isEmpty()) { + $schema->properties = null; + } + $properties->lock(); + } + + return $schemaWrapper; + } + + /** + * @return Properties|static|null + */ + public static function properties() + { + return static::schema()->getProperties(); + } + + /** + * @param mixed $data + * @param Context $options + * @return static|mixed + * @throws \Swaggest\JsonSchema\Exception + * @throws \Swaggest\JsonSchema\InvalidValue + */ + public static function import($data, Context $options = null) + { + return static::schema()->in($data, $options); + } + + /** + * @param mixed $data + * @param Context $options + * @return mixed + * @throws \Swaggest\JsonSchema\InvalidValue + * @throws \Exception + */ + public static function export($data, Context $options = null) + { + return static::schema()->out($data, $options); + } + + /** + * @param ObjectItemContract $objectItem + * @return static + */ + public static function pick(ObjectItemContract $objectItem) + { + $className = get_called_class(); + return $objectItem->getNestedObject($className); + } + + /** + * @return static + */ + public static function create() + { + return new static; + } + + protected $__validateOnSet = true; // todo skip validation during import + + /** + * @return \stdClass + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $result = new \stdClass(); + $schema = static::schema(); + $properties = $schema->getProperties(); + $processed = array(); + if (null !== $properties) { + foreach ($properties->getDataKeyMap() as $propertyName => $dataName) { + $value = $this->$propertyName ?? null; + + // Value is exported if exists. + if (null !== $value || array_key_exists($propertyName, $this->__arrayOfData)) { + $result->$dataName = $value; + $processed[$propertyName] = true; + continue; + } + + // Non-existent value is only exported if belongs to nullable property (having 'null' in type array). + $property = $schema->getProperty($propertyName); + if ($property instanceof Schema) { + $types = $property->type; + if ($types === Schema::NULL || (is_array($types) && in_array(Schema::NULL, $types))) { + $result->$dataName = $value; + } + } + } + } + foreach ($schema->getNestedPropertyNames() as $name) { + /** @var ObjectItem $nested */ + $nested = $this->$name; + if (null !== $nested) { + foreach ((array)$nested->jsonSerialize() as $key => $value) { + $result->$key = $value; + } + } + } + + if (!empty($this->__arrayOfData)) { + foreach ($this->__arrayOfData as $name => $value) { + if (!isset($processed[$name])) { + $result->$name = $this->{$name}; + } + } + } + + return $result; + } + + /** + * @return static|NameMirror + */ + public static function names(Properties $properties = null, $mapping = Schema::DEFAULT_MAPPING) + { + if ($properties !== null) { + return new NameMirror($properties->getDataKeyMap($mapping)); + } + + static $nameflector = null; + if (null === $nameflector) { + $nameflector = new NameMirror(); + } + return $nameflector; + } + + public function __set($name, $column) // todo nested schemas + { + if ($this->__validateOnSet) { + if ($property = static::schema()->getProperty($name)) { + $property->out($column); + } + } + $this->__arrayOfData[$name] = $column; + return $this; + } + + public static function className() + { + return get_called_class(); + } + + /** + * @throws \Exception + * @throws \Swaggest\JsonSchema\InvalidValue + */ + public function validate() + { + static::schema()->out($this); + } +} + diff --git a/languages/php/src/schemas/ClientSettings.php b/languages/php/src/schemas/ClientSettings.php new file mode 100644 index 000000000..c27cc3322 --- /dev/null +++ b/languages/php/src/schemas/ClientSettings.php @@ -0,0 +1,133 @@ +identityUrl = Schema::string(); + $properties->identityUrl->description = "The identity url of the targeted Bitwarden instance. Defaults to `https://identity.bitwarden.com`"; + $properties->identityUrl->default = "https://identity.bitwarden.com"; + $properties->apiUrl = Schema::string(); + $properties->apiUrl->description = "The api url of the targeted Bitwarden instance. Defaults to `https://api.bitwarden.com`"; + $properties->apiUrl->default = "https://api.bitwarden.com"; + $properties->userAgent = Schema::string(); + $properties->userAgent->description = "The user_agent to sent to Bitwarden. Defaults to `Bitwarden Rust-SDK`"; + $properties->userAgent->default = "Bitwarden Rust-SDK"; + $properties->deviceType = new Schema(); + $propertiesDeviceTypeAllOf0 = Schema::string(); + $propertiesDeviceTypeAllOf0->enum = array( + self::ANDROID, + self::I_OS, + self::CHROME_EXTENSION, + self::FIREFOX_EXTENSION, + self::OPERA_EXTENSION, + self::EDGE_EXTENSION, + self::WINDOWS_DESKTOP, + self::MAC_OS_DESKTOP, + self::LINUX_DESKTOP, + self::CHROME_BROWSER, + self::FIREFOX_BROWSER, + self::OPERA_BROWSER, + self::EDGE_BROWSER, + self::IE_BROWSER, + self::UNKNOWN_BROWSER, + self::ANDROID_AMAZON, + self::UWP, + self::SAFARI_BROWSER, + self::VIVALDI_BROWSER, + self::VIVALDI_EXTENSION, + self::SAFARI_EXTENSION, + self::SDK, + ); + $propertiesDeviceTypeAllOf0->setFromRef('#/definitions/DeviceType'); + $properties->deviceType->allOf[0] = $propertiesDeviceTypeAllOf0; + $properties->deviceType->description = "Device type to send to Bitwarden. Defaults to SDK"; + $properties->deviceType->default = "SDK"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->schema = "http://json-schema.org/draft-07/schema#"; + $ownerSchema->title = "ClientSettings"; + $ownerSchema->description = "Basic client behavior settings. These settings specify the various targets and behavior of the Bitwarden Client. They are optional and uneditable once the client is initialized.\n\nDefaults to\n\n``` # use bitwarden::client::client_settings::{ClientSettings, DeviceType}; # use assert_matches::assert_matches; let settings = ClientSettings { identity_url: \"https://identity.bitwarden.com\".to_string(), api_url: \"https://api.bitwarden.com\".to_string(), user_agent: \"Bitwarden Rust-SDK\".to_string(), device_type: DeviceType::SDK, }; let default = ClientSettings::default(); assert_matches!(settings, default); ```\n\nTargets `localhost:8080` for debug builds."; + } +} diff --git a/languages/php/src/schemas/Command.php b/languages/php/src/schemas/Command.php new file mode 100644 index 000000000..cbd649c2f --- /dev/null +++ b/languages/php/src/schemas/Command.php @@ -0,0 +1,44 @@ +projects = ProjectsCommand::schema(); + $properties->secrets = SecretsCommand::schema(); + $properties->accessTokenLogin = AccessTokenLoginRequest::schema(); + + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + + $ownerSchema->oneOf = array( + self::names()->projects, + self::names()->secrets, + self::names()->accessTokenLogin, + ); + } +} diff --git a/languages/php/src/schemas/ProjectCreateRequest.php b/languages/php/src/schemas/ProjectCreateRequest.php new file mode 100644 index 000000000..6a4e0f082 --- /dev/null +++ b/languages/php/src/schemas/ProjectCreateRequest.php @@ -0,0 +1,43 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization where the project will be created"; + $properties->organizationId->format = "uuid"; + $properties->name = Schema::string(); + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->name, + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/ProjectCreateRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectGetRequest.php b/languages/php/src/schemas/ProjectGetRequest.php new file mode 100644 index 000000000..972bf18ec --- /dev/null +++ b/languages/php/src/schemas/ProjectGetRequest.php @@ -0,0 +1,37 @@ +id = Schema::string(); + $properties->id->description = "ID of the project to retrieve"; + $properties->id->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + ); + $ownerSchema->setFromRef('#/definitions/ProjectGetRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectPutRequest.php b/languages/php/src/schemas/ProjectPutRequest.php new file mode 100644 index 000000000..96b9705e7 --- /dev/null +++ b/languages/php/src/schemas/ProjectPutRequest.php @@ -0,0 +1,50 @@ +id = Schema::string(); + $properties->id->description = "ID of the project to modify"; + $properties->id->format = "uuid"; + $properties->organizationId = Schema::string(); + $properties->organizationId->description = "Organization ID of the project to modify"; + $properties->organizationId->format = "uuid"; + $properties->name = Schema::string(); + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + self::names()->name, + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/ProjectPutRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectsCommand.php b/languages/php/src/schemas/ProjectsCommand.php new file mode 100644 index 000000000..22645db3c --- /dev/null +++ b/languages/php/src/schemas/ProjectsCommand.php @@ -0,0 +1,55 @@ + Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the projects whose IDs match the provided ones + * + * Returns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse) + */ +class ProjectsCommand extends BitwardenClassStructure +{ + public ?\stdClass $delete; + + public ?\stdClass $get; + + public ?\stdClass $list; + + public ?\stdClass $create; + + public ?\stdClass $update; + + + /** + * @param Properties|static $properties + * @param Schema $ownerSchema + */ + public static function setUpProperties($properties, Schema $ownerSchema) + { + $properties->delete = ProjectsDeleteRequest::schema() ? ProjectsDeleteRequest::schema() : null; + $properties->get = ProjectGetRequest::schema() ? ProjectGetRequest::schema() : null; + $properties->list = ProjectsListRequest::schema() ? ProjectsListRequest::schema() : null; + $properties->update = ProjectPutRequest::schema() ? ProjectPutRequest::schema() : null; + $properties->create = ProjectCreateRequest::schema() ? ProjectCreateRequest::schema() : null; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->description = "> Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the projects whose IDs match the provided ones\n\nReturns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse)"; + + $ownerSchema->oneOf = array( + self::names()->create, + self::names()->delete, + self::names()->get, + self::names()->list, + self::names()->update, + ); + } +} diff --git a/languages/php/src/schemas/ProjectsDeleteRequest.php b/languages/php/src/schemas/ProjectsDeleteRequest.php new file mode 100644 index 000000000..87a7cfad7 --- /dev/null +++ b/languages/php/src/schemas/ProjectsDeleteRequest.php @@ -0,0 +1,39 @@ +ids = Schema::arr(); + $properties->ids->items = Schema::string(); + $properties->ids->items->format = "uuid"; + $properties->ids->description = "IDs of the projects to delete"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->ids, + ); + $ownerSchema->setFromRef('#/definitions/ProjectsDeleteRequest'); + } +} diff --git a/languages/php/src/schemas/ProjectsListRequest.php b/languages/php/src/schemas/ProjectsListRequest.php new file mode 100644 index 000000000..cc1a9474f --- /dev/null +++ b/languages/php/src/schemas/ProjectsListRequest.php @@ -0,0 +1,38 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization to retrieve all the projects from"; + $properties->organizationId->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/ProjectsListRequest'); + } +} diff --git a/languages/php/src/schemas/SecretCreateRequest.php b/languages/php/src/schemas/SecretCreateRequest.php new file mode 100644 index 000000000..d34b36e98 --- /dev/null +++ b/languages/php/src/schemas/SecretCreateRequest.php @@ -0,0 +1,58 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization where the secret will be created"; + $properties->organizationId->format = "uuid"; + $properties->key = Schema::string(); + $properties->value = Schema::string(); + $properties->note = Schema::string(); + $properties->projectIds = (new Schema())->setType([Schema::_ARRAY, Schema::NULL]); + $properties->projectIds->items = Schema::string(); + $properties->projectIds->items->format = "uuid"; + $properties->projectIds->description = "IDs of the projects that this secret will belong to"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->key, + self::names()->note, + self::names()->organizationId, + self::names()->value, + ); + $ownerSchema->setFromRef('#/definitions/SecretCreateRequest'); + } +} diff --git a/languages/php/src/schemas/SecretGetRequest.php b/languages/php/src/schemas/SecretGetRequest.php new file mode 100644 index 000000000..f31f7cad3 --- /dev/null +++ b/languages/php/src/schemas/SecretGetRequest.php @@ -0,0 +1,38 @@ +id = Schema::string(); + $properties->id->description = "ID of the secret to retrieve"; + $properties->id->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + ); + $ownerSchema->setFromRef('#/definitions/SecretGetRequest'); + } +} diff --git a/languages/php/src/schemas/SecretIdentifiersRequest.php b/languages/php/src/schemas/SecretIdentifiersRequest.php new file mode 100644 index 000000000..b4e75b801 --- /dev/null +++ b/languages/php/src/schemas/SecretIdentifiersRequest.php @@ -0,0 +1,38 @@ +organizationId = Schema::string(); + $properties->organizationId->description = "Organization to retrieve all the secrets from"; + $properties->organizationId->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->organizationId, + ); + $ownerSchema->setFromRef('#/definitions/SecretIdentifiersRequest'); + } +} diff --git a/languages/php/src/schemas/SecretPutRequest.php b/languages/php/src/schemas/SecretPutRequest.php new file mode 100644 index 000000000..d890a909d --- /dev/null +++ b/languages/php/src/schemas/SecretPutRequest.php @@ -0,0 +1,64 @@ +id = Schema::string(); + $properties->id->description = "ID of the secret to modify"; + $properties->id->format = "uuid"; + $properties->organizationId = Schema::string(); + $properties->organizationId->description = "Organization ID of the secret to modify"; + $properties->organizationId->format = "uuid"; + $properties->key = Schema::string(); + $properties->value = Schema::string(); + $properties->note = Schema::string(); + $properties->projectIds = (new Schema())->setType([Schema::_ARRAY, Schema::NULL]); + $properties->projectIds->items = Schema::string(); + $properties->projectIds->items->format = "uuid"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->id, + self::names()->key, + self::names()->note, + self::names()->organizationId, + self::names()->value, + ); + $ownerSchema->setFromRef('#/definitions/SecretPutRequest'); + } +} diff --git a/languages/php/src/schemas/SecretVerificationRequest.php b/languages/php/src/schemas/SecretVerificationRequest.php new file mode 100644 index 000000000..95cfd1e15 --- /dev/null +++ b/languages/php/src/schemas/SecretVerificationRequest.php @@ -0,0 +1,35 @@ +masterPassword = (new Schema())->setType([Schema::STRING, Schema::NULL]); + $properties->masterPassword->description = "The user's master password to use for user verification. If supplied, this will be used for verification purposes."; + $properties->otp = (new Schema())->setType([Schema::STRING, Schema::NULL]); + $properties->otp->description = "Alternate user verification method through OTP. This is provided for users who have no master password due to use of Customer Managed Encryption. Must be present and valid if master_password is absent."; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->setFromRef('#/definitions/SecretVerificationRequest'); + } +} diff --git a/languages/php/src/schemas/SecretsCommand.php b/languages/php/src/schemas/SecretsCommand.php new file mode 100644 index 000000000..1ed8c97c5 --- /dev/null +++ b/languages/php/src/schemas/SecretsCommand.php @@ -0,0 +1,56 @@ + Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the secrets whose IDs match the provided ones + * + * Returns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse) + */ +class SecretsCommand extends BitwardenClassStructure +{ + public ?\stdClass $delete; + + public ?\stdClass $get; + + public ?\stdClass $getByIds; + + public ?\stdClass $list; + + public ?\stdClass $create; + + public ?\stdClass $put; + + /** + * @param Properties|static $properties + * @param Schema $ownerSchema + */ + public static function setUpProperties($properties, Schema $ownerSchema) + { + $properties->delete = SecretsDeleteRequest::schema() ? SecretsDeleteRequest::schema() : null; + $properties->getByIds = SecretsGetRequest::schema() ? SecretGetRequest::schema() : null; + $properties->create = SecretCreateRequest::schema() ? SecretCreateRequest::schema() : null; + $properties->put = SecretPutRequest::schema() ? SecretPutRequest::schema() : null; + $properties->list = SecretIdentifiersRequest::schema() ? SecretIdentifiersRequest::schema() : null; + $properties->get = SecretsGetRequest::schema() ? SecretGetRequest::schema() : null; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->description = "> Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the secrets whose IDs match the provided ones\n\nReturns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse)"; + $ownerSchema->oneOf = array( + self::names()->create, + self::names()->put, + self::names()->list, + self::names()->getByIds, + self::names()->delete, + ); + } +} diff --git a/languages/php/src/schemas/SecretsDeleteRequest.php b/languages/php/src/schemas/SecretsDeleteRequest.php new file mode 100644 index 000000000..35138fcb1 --- /dev/null +++ b/languages/php/src/schemas/SecretsDeleteRequest.php @@ -0,0 +1,39 @@ +ids = Schema::arr(); + $properties->ids->items = Schema::string(); + $properties->ids->items->format = "uuid"; + $properties->ids->description = "IDs of the secrets to delete"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->ids, + ); + $ownerSchema->setFromRef('#/definitions/SecretsDeleteRequest'); + } +} diff --git a/languages/php/src/schemas/SecretsGetRequest.php b/languages/php/src/schemas/SecretsGetRequest.php new file mode 100644 index 000000000..4758dabf4 --- /dev/null +++ b/languages/php/src/schemas/SecretsGetRequest.php @@ -0,0 +1,39 @@ +ids = Schema::arr(); + $properties->ids->items = Schema::string(); + $properties->ids->items->format = "uuid"; + $properties->ids->description = "IDs of the secrets to retrieve"; + $ownerSchema->type = Schema::OBJECT; + $ownerSchema->additionalProperties = false; + $ownerSchema->required = array( + self::names()->ids, + ); + $ownerSchema->setFromRef('#/definitions/SecretsGetRequest'); + } +} diff --git a/languages/python/.gitignore b/languages/python/.gitignore index baa13a078..495f6296b 100644 --- a/languages/python/.gitignore +++ b/languages/python/.gitignore @@ -1,3 +1,4 @@ *.egg-info bitwarden_py*.so __pycache__ +.venv diff --git a/languages/python/BitwardenClient/__init__.py b/languages/python/BitwardenClient/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/languages/python/BitwardenClient/bitwarden_client.py b/languages/python/BitwardenClient/bitwarden_client.py deleted file mode 100644 index 6a0a77ed8..000000000 --- a/languages/python/BitwardenClient/bitwarden_client.py +++ /dev/null @@ -1,84 +0,0 @@ -import json -from typing import Any, List -import bitwarden_py -from .schemas import ClientSettings, Command, PasswordLoginRequest, PasswordLoginResponse, ResponseForPasswordLoginResponse, ResponseForSecretIdentifiersResponse, ResponseForSecretResponse, ResponseForSecretsDeleteResponse, ResponseForSyncResponse, ResponseForUserAPIKeyResponse, SecretCreateRequest, SecretGetRequest, SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse, SecretVerificationRequest, SecretsCommand, SecretsDeleteRequest, SecretsDeleteResponse, SyncRequest, SyncResponse, UserAPIKeyResponse - - -class BitwardenClient: - def __init__(self, settings: ClientSettings = None): - if settings is None: - self.inner = bitwarden_py.BitwardenClient(None) - else: - settings_json = json.dumps(settings.to_dict()) - self.inner = bitwarden_py.BitwardenClient(settings_json) - - def password_login(self, email: str, password: str) -> ResponseForPasswordLoginResponse: - result = self._run_command( - Command(password_login=PasswordLoginRequest(email, password)) - ) - return ResponseForPasswordLoginResponse.from_dict(result) - - def get_user_api_key(self, secret: str, is_otp: bool = False) -> ResponseForUserAPIKeyResponse: - result = self._run_command( - Command(get_user_api_key=SecretVerificationRequest( - secret if not is_otp else None, secret if is_otp else None)) - ) - return ResponseForUserAPIKeyResponse.from_dict(result) - - def sync(self, exclude_subdomains: bool = False) -> ResponseForSyncResponse: - result = self._run_command( - Command(sync=SyncRequest(exclude_subdomains)) - ) - return ResponseForSyncResponse.from_dict(result) - - def secrets(self): - return SecretsClient(self) - - def _run_command(self, command: Command) -> Any: - response_json = self.inner.run_command(json.dumps(command.to_dict())) - return json.loads(response_json) - - -class SecretsClient: - def __init__(self, client: BitwardenClient): - self.client = client - - def get(self, id: str) -> ResponseForSecretResponse: - result = self.client._run_command( - Command(secrets=SecretsCommand(get=SecretGetRequest(id))) - ) - return ResponseForSecretResponse.from_dict(result) - - def create(self, key: str, - note: str, - organization_id: str, - value: str) -> ResponseForSecretResponse: - result = self.client._run_command( - Command(secrets=SecretsCommand( - create=SecretCreateRequest(key, note, organization_id, value))) - ) - return ResponseForSecretResponse.from_dict(result) - - def list(self, organization_id: str) -> ResponseForSecretIdentifiersResponse: - result = self.client._run_command( - Command(secrets=SecretsCommand( - list=SecretIdentifiersRequest(organization_id))) - ) - return ResponseForSecretIdentifiersResponse.from_dict(result) - - def update(self, id: str, - key: str, - note: str, - organization_id: str, - value: str) -> ResponseForSecretResponse: - result = self.client._run_command( - Command(secrets=SecretsCommand(update=SecretPutRequest( - id, key, note, organization_id, value))) - ) - return ResponseForSecretResponse.from_dict(result) - - def delete(self, ids: List[str]) -> ResponseForSecretsDeleteResponse: - result = self.client._run_command( - Command(secrets=SecretsCommand(delete=SecretsDeleteRequest(ids))) - ) - return ResponseForSecretsDeleteResponse.from_dict(result) diff --git a/languages/python/README.md b/languages/python/README.md index 03a4ce572..e5fe5ae70 100644 --- a/languages/python/README.md +++ b/languages/python/README.md @@ -1,27 +1,58 @@ -# Requirements +# Build locally +## Requirements -- Python3 -- setuptools - ```bash - pip install setuptools - ``` -- setuptools_rust - ```bash - pip install setuptools_rust - ``` +- Python 3 +- `maturin` (install with `pip install maturin`) +- `npm` -# Installation - -From the `languages/python/` directory, +## Build +From the root of the repository: ```bash -python3 ./setup.py develop +npm run schemas # generate schemas.py + +cd languages/python/ +maturin develop ``` -Move the the resulting `.so` file to `bitwarden_py.so`, if it isn't already there. +You can now import `BitwardenClient` in your Python code with: +```python +from bitwarden_sdk import BitwardenClient +``` + +# Use without building locally + +```bash +pip install bitwarden-sdk +``` # Run +Set the `ORGANIZATION_ID` and `ACCESS_TOKEN` environment variables to your organization ID and access token, respectively. + +```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 -python3 ./login.py +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 new file mode 100644 index 000000000..b2aeffea1 --- /dev/null +++ b/languages/python/bitwarden_sdk/__init__.py @@ -0,0 +1,13 @@ +"""The official Bitwarden client library for Python.""" + +__version__ = "0.1.0" + +from .bitwarden_client import * +from .schemas import * + +__doc__ = bitwarden_client.__doc__ +if hasattr(bitwarden_client, "__all__"): + __all__ = bitwarden_client.__all__ + +if hasattr(schemas, "__all__"): + __all__ += schemas.__all__ diff --git a/languages/python/bitwarden_sdk/bitwarden_client.py b/languages/python/bitwarden_sdk/bitwarden_client.py new file mode 100644 index 000000000..b5ea48c44 --- /dev/null +++ b/languages/python/bitwarden_sdk/bitwarden_client.py @@ -0,0 +1,125 @@ +import json +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 + +class BitwardenClient: + def __init__(self, settings: ClientSettings = None): + if settings is None: + self.inner = bitwarden_py.BitwardenClient(None) + else: + 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 secrets(self): + return SecretsClient(self) + + def projects(self): + return ProjectsClient(self) + + def _run_command(self, command: Command) -> Any: + response_json = self.inner.run_command(json.dumps(command.to_dict())) + response = json.loads(response_json) + + if response["success"] == False: + raise Exception(response["errorMessage"]) + + return response + +class SecretsClient: + def __init__(self, client: BitwardenClient): + self.client = client + + def get(self, id: str) -> ResponseForSecretResponse: + result = self.client._run_command( + Command(secrets=SecretsCommand(get=SecretGetRequest(id))) + ) + return ResponseForSecretResponse.from_dict(result) + + def create(self, key: str, + note: str, + organization_id: str, + value: str, + project_ids: Optional[List[UUID]] = None + ) -> ResponseForSecretResponse: + result = self.client._run_command( + Command(secrets=SecretsCommand( + create=SecretCreateRequest(key, note, organization_id, value, project_ids))) + ) + return ResponseForSecretResponse.from_dict(result) + + def list(self, organization_id: str) -> ResponseForSecretIdentifiersResponse: + result = self.client._run_command( + Command(secrets=SecretsCommand( + list=SecretIdentifiersRequest(organization_id))) + ) + 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: + result = self.client._run_command( + Command(secrets=SecretsCommand(update=SecretPutRequest( + id, key, note, organization_id, value, project_ids))) + ) + return ResponseForSecretResponse.from_dict(result) + + def delete(self, ids: List[str]) -> ResponseForSecretsDeleteResponse: + result = self.client._run_command( + Command(secrets=SecretsCommand(delete=SecretsDeleteRequest(ids))) + ) + return ResponseForSecretsDeleteResponse.from_dict(result) + +class ProjectsClient: + def __init__(self, client: BitwardenClient): + self.client = client + + def get(self, id: str) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(get=ProjectGetRequest(id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def create(self, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + create=ProjectCreateRequest(name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def list(self, organization_id: str) -> ResponseForProjectsResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand( + list=ProjectsListRequest(organization_id))) + ) + return ResponseForProjectsResponse.from_dict(result) + + def update(self, id: str, + name: str, + organization_id: str, + ) -> ResponseForProjectResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(update=ProjectPutRequest( + id, name, organization_id))) + ) + return ResponseForProjectResponse.from_dict(result) + + def delete(self, ids: List[str]) -> ResponseForProjectsDeleteResponse: + result = self.client._run_command( + Command(projects=ProjectsCommand(delete=ProjectsDeleteRequest(ids))) + ) + return ResponseForProjectsDeleteResponse.from_dict(result) diff --git a/languages/python/example.py b/languages/python/example.py new file mode 100755 index 000000000..16367a0c5 --- /dev/null +++ b/languages/python/example.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +import logging +import os + +from bitwarden_sdk import BitwardenClient, DeviceType, client_settings_from_dict + +# Create the BitwardenClient, which is used to interact with the SDK +client = BitwardenClient( + client_settings_from_dict( + { + "apiUrl": os.getenv("API_URL", "http://localhost:4000"), + "deviceType": DeviceType.SDK, + "identityUrl": os.getenv("IDENTITY_URL", "http://localhost:33656"), + "userAgent": "Python", + } + ) +) + +# Add some logging & set the org id +logging.basicConfig(level=logging.DEBUG) +organization_id = os.getenv("ORGANIZATION_ID") + +# Attempt to authenticate with the Secrets Manager Access Token +client.access_token_login(os.getenv("ACCESS_TOKEN")) + +# -- Example Project Commands -- + +project = client.projects().create("ProjectName", organization_id) +project2 = client.projects().create("Project - Don't Delete Me!", organization_id) +updated_project = client.projects().update( + project.data.id, "Cool New Project Name", organization_id +) +get_that_project = client.projects().get(project.data.id) + +input("Press Enter to delete the project...") +client.projects().delete([project.data.id]) + +print(client.projects().list(organization_id)) + +# -- Example Secret Commands -- + +secret = client.secrets().create( + "TEST_SECRET", + "This is a test secret", + organization_id, + "Secret1234!", + [project2.data.id], +) +secret2 = client.secrets().create( + "Secret - Don't Delete Me!", + "This is a test secret that will stay", + organization_id, + "Secret1234!", + [project2.data.id], +) +secret_updated = client.secrets().update( + secret.data.id, + "TEST_SECRET_UPDATED", + "This as an updated test secret", + organization_id, + "Secret1234!_updated", + [project2.data.id], +) +secret_retrieved = client.secrets().get(secret.data.id) + +input("Press Enter to delete the secret...") +client.secrets().delete([secret.data.id]) + +print(client.secrets().list(organization_id)) diff --git a/languages/python/login.py b/languages/python/login.py deleted file mode 100644 index 9b756a005..000000000 --- a/languages/python/login.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -import logging -from BitwardenClient.bitwarden_client import BitwardenClient -from BitwardenClient.schemas import client_settings_from_dict - -client = BitwardenClient(client_settings_from_dict({ - "api_url": "http://localhost:4000", - "identity_url": "http://localhost:33656", - "user_agent": "Python", -})) - -logging.basicConfig(level=logging.DEBUG) - -result = client.password_login("test@bitwarden.com", "asdfasdf") -print(result) -print(client.get_user_api_key("asdfasdf")) - -sync = client.sync() - -secret = client.secrets().create("TEST_SECRET", "This is a test secret", - sync.data.profile.organizations[0].id, "Secret1234!") -print(secret) - -client.secrets().delete([secret.data.id]) diff --git a/languages/python/pyproject.toml b/languages/python/pyproject.toml new file mode 100644 index 000000000..28bb22507 --- /dev/null +++ b/languages/python/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +build-backend = "maturin" +requires = ["maturin>=1.0,<2.0", "setuptools_rust>=1.8.1"] + +[project] +authors = [{ name = "Bitwarden", email = "support@bitwarden.com" }] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: Other/Proprietary License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Rust", + "Topic :: Security", +] +dependencies = ["dateutils >= 0.6.6"] +description = "A Bitwarden Client for python" +name = "bitwarden_sdk" +readme = "README.md" +requires-python = ">=3.0" +version = "0.1.0" + +[tool.maturin] +bindings = "pyo3" +compatibility = "2_28" +include = [ + { path = "bitwarden_sdk/*.py", format = ["sdist", "wheel"] } +] +manifest-path = "../../crates/bitwarden-py/Cargo.toml" +python-packages = ["bitwarden_sdk"] diff --git a/languages/python/setup.py b/languages/python/setup.py deleted file mode 100644 index 7976ebcba..000000000 --- a/languages/python/setup.py +++ /dev/null @@ -1,12 +0,0 @@ -from setuptools import setup -from setuptools_rust import Binding, RustExtension - -setup( - name="BitwardenClient", - description="A Bitwarden Client for python", - version="0.1", - rust_extensions=[RustExtension( - "bitwarden_py", path="../../crates/bitwarden-py/Cargo.toml", binding=Binding.PyO3)], - packages=['bitwardenclient'], - zip_safe=False, -) diff --git a/languages/ruby/.gitignore b/languages/ruby/.gitignore new file mode 100644 index 000000000..92b76b424 --- /dev/null +++ b/languages/ruby/.gitignore @@ -0,0 +1,4 @@ +*.lock +*.gem +bitwarden_sdk_secrets/lib/schemas.rb +bitwarden_sdk_secrets/pkg diff --git a/languages/ruby/CHANGELOG.md b/languages/ruby/CHANGELOG.md new file mode 100644 index 000000000..0c842ecbb --- /dev/null +++ b/languages/ruby/CHANGELOG.md @@ -0,0 +1,6 @@ +## [Unreleased] + +## [0.1.0] - 2024-02-23 + +- Initial release + diff --git a/languages/ruby/README.md b/languages/ruby/README.md new file mode 100644 index 000000000..e9fb61e0c --- /dev/null +++ b/languages/ruby/README.md @@ -0,0 +1,121 @@ +# Bitwarden Secrets Manager SDK + +Ruby bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might +be missing some functionality. + +## Installation + +Requirements: Ruby >= 3.0 + +Install gem: `gem install bitwarden-sdk-secrets` + +Import it: require 'bitwarden-sdk-secrets' + +## Usage + +To interact with client first you need to obtain access token from Bitwarden. Client will be +initialized with default client settings if they are not provided via env variables. + +```ruby +require 'bitwarden-sdk-secrets' + +# then you can initialize BitwardenSettings: +bitwarden_settings = BitwardenSDK::BitwardenSettings.new( + 'https://api.bitwarden.com', + 'https://identity.bitwarden.com' +) + +# By passing these setting you can initialize BitwardenClient + +bw_client = BitwardenSDK::BitwardenClient.new(bitwarden_settings) +response = bw_client.access_token_login(token) +puts response +``` + +After successful authorization you can interact with client to manage your projects and secrets. + +```ruby + +# CREATE project +project_name = 'Test project 1' +response = bw_client.project_client.create_project(project_name, organization_id) +puts response +project_id = response['id'] + +# GET project +response = bw_client.project_client.get(project_id) +puts response + +# LIST projects +response = bw_client.project_client.list_projects(organization_id) +puts response + +# UPDATE projects +name = 'Updated test project 1' +response = bw_client.project_client.update_project(project_id, name, organization_id) +puts response + +# DELETE project +response = bw_client.project_client.delete_projects([project_id]) +puts response +``` + +Similarly, you interact with secrets: + +```ruby +# CREATE secret +key = 'AWS-SES' +note = 'Private account' +value = '8t27.dfj;' +response = bw_client.secrets_client.create(key, note, organization_id, [project_id], value) +puts response +secret_id = response['id'] + +# GET secret +response = bw_client.secrets_client.get(secret_id) +puts response + +# GET secret by ids +response = bw_client.secrets_client.get_by_ids([secret_id]) +puts response + +# LIST secrets +response = bw_client.secrets_client.list(organization_id) +puts response + +# UPDATE secret +note = 'updated password' +value = '7I.ert10AjK' +response = bw_client.secrets_client.update(secret_id, key, note,organization_id, [project_id], value) +puts response + +# DELETE secret +response = bw_client.secrets_client.delete_secret([secret_id]) +puts response +``` + +## Development + +```bash +# Build and copy the bitwarden-c library +cargo build --package bitwarden-c +cp ../../target/debug/libbitwarden_c.dylib ./bitwarden_sdk_secrets/lib/macos-arm64/libbitwarden_c.dylib + +# Install ruby dependencies +cd ./bitwarden_sdk_secrets +bundle install + +# Install the gem +bundle exec rake install + +## Run example tests +cd .. +export ACCESS_TOKEN="" +export ORGANIZATION_ID="" + +export API_URL=https://localhost:8080/api +export IDENTITY_URL=https://localhost:8080/identity +ruby examples/example.rb +``` + +[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/ruby/bitwarden_sdk_secrets/Gemfile b/languages/ruby/bitwarden_sdk_secrets/Gemfile new file mode 100644 index 000000000..0f3963489 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/Gemfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# Specify your gem's dependencies in exmp.gemspec +gemspec + +gem "rake", "~> 13.0" +gem "rspec", "~> 3.0" +gem "rubocop", "~> 1.21" diff --git a/languages/ruby/bitwarden_sdk_secrets/Rakefile b/languages/ruby/bitwarden_sdk_secrets/Rakefile new file mode 100644 index 000000000..30550c5de --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/Rakefile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rubocop/rake_task" +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new + +RuboCop::RakeTask.new + +task default: :rubocop diff --git a/languages/ruby/bitwarden_sdk_secrets/bitwarden-sdk-secrets.gemspec b/languages/ruby/bitwarden_sdk_secrets/bitwarden-sdk-secrets.gemspec new file mode 100644 index 000000000..457e60a3e --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/bitwarden-sdk-secrets.gemspec @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative 'lib/version' + +Gem::Specification.new do |spec| + spec.name = 'bitwarden-sdk-secrets' + spec.version = BitwardenSDKSecrets::VERSION + spec.authors = ['Bitwarden Inc.'] + spec.email = ['hello@bitwarden_sdk.com'] + + spec.summary = 'Bitwarden Secrets Manager SDK.' + spec.description = 'Ruby wrapper for Bitwarden secrets manager SDK.' + spec.homepage = 'https://bitwarden.com/products/secrets-manager/' + spec.required_ruby_version = '>= 3.0.0' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/bitwarden/sdk' + spec.metadata['changelog_uri'] = 'https://github.com/bitwarden/sdk/blob/main/languages/ruby/CHANGELOG.md' + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git Gemfile]) + end + end + + spec.files += Dir.glob('lib/linux-x64/**/*') + spec.files += Dir.glob('lib/macos-x64/**/*') + spec.files += Dir.glob('lib/windows-x64/**/*') + spec.files += Dir.glob('lib/macos-arm64/**/*') + spec.files += Dir.glob('lib/schemas.rb') + + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + # Uncomment to register a new dependency of your gem + # spec.add_dependency "example-gem", "~> 1.0" + spec.add_dependency 'dry-struct', '~> 1.6' + spec.add_dependency 'dry-types', '~> 1.7' + spec.add_dependency 'ffi', '~> 1.15' + spec.add_dependency 'json', '~> 2.6' + spec.add_dependency 'rake', '~> 13.0' + spec.add_dependency 'rubocop', '~> 1.21' + +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden-sdk-secrets.rb b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden-sdk-secrets.rb new file mode 100644 index 000000000..15cd115d1 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden-sdk-secrets.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'json' +require 'dry-types' + +require_relative 'schemas' +require_relative 'extended_schemas/schemas' +require_relative 'command_runner' +require_relative 'bitwarden_lib' +require_relative 'bitwarden_error' +require_relative 'projects' +require_relative 'secrets' + +module BitwardenSDKSecrets + class BitwardenSettings + attr_accessor :api_url, :identity_url + + def initialize(api_url, identity_url) + # if api_url.nil? || identity_url.nil? + # raise ArgumentError, "api_url and identity_url cannot be nil" + # end + + @api_url = api_url + @identity_url = identity_url + end + end + + class BitwardenClient + attr_reader :bitwarden, :project_client, :secrets_client + + def initialize(bitwarden_settings) + client_settings = ClientSettings.new( + api_url: bitwarden_settings.api_url, + identity_url: bitwarden_settings.identity_url, + user_agent: 'Bitwarden RUBY-SDK', + device_type: nil + ) + + @bitwarden = BitwardenLib + @handle = @bitwarden.init(client_settings.to_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 + end + + def free_mem + @bitwarden.free_mem(@handle) + end + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden_error.rb b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden_error.rb new file mode 100644 index 000000000..b2239e29b --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden_error.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module BitwardenSDKSecrets + class BitwardenError < StandardError + def initialize(message = 'Error getting response') + super(message) + end + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden_lib.rb b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden_lib.rb new file mode 100644 index 000000000..05ab51421 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/bitwarden_lib.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'ffi' + +module BitwardenSDKSecrets + module BitwardenLib + extend FFI::Library + + def self.mac_with_intel? + `uname -m`.strip == 'x86_64' + end + + ffi_lib case RUBY_PLATFORM + when /darwin/ + local_file = if mac_with_intel? + File.expand_path('macos-x64/libbitwarden_c.dylib', __dir__) + else + File.expand_path('macos-arm64/libbitwarden_c.dylib', __dir__) + end + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libbitwarden_c.dylib', __dir__) + when /linux/ + local_file = File.expand_path('linux-x64/libbitwarden_c.so', __dir__) + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libbitwarden_c.so', __dir__) + when /mswin|mingw/ + local_file = File.expand_path('windows-x64/bitwarden_c.dll', __dir__) + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/bitwarden_c.dll', __dir__) + else + raise "Unsupported platform: #{RUBY_PLATFORM}" + end + + attach_function :init, [:string], :pointer + attach_function :run_command, %i[string pointer], :string + attach_function :free_mem, [:pointer], :void + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/command_runner.rb b/languages/ruby/bitwarden_sdk_secrets/lib/command_runner.rb new file mode 100644 index 000000000..80e846a80 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/command_runner.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module BitwardenSDKSecrets + class CommandRunner + def initialize(bitwarden_sdk, handle) + @bitwarden_sdk = bitwarden_sdk + @handle = handle + end + + # @param [Dry-Struct] cmd + def run(cmd) + @bitwarden_sdk.run_command(cmd.to_json, @handle) + end + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/extended_schemas/schemas.rb b/languages/ruby/bitwarden_sdk_secrets/lib/extended_schemas/schemas.rb new file mode 100644 index 000000000..e2352237f --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/extended_schemas/schemas.rb @@ -0,0 +1,64 @@ + +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 :get_user_api_key, SecretVerificationRequest.optional.default(nil) + attribute :fingerprint, FingerprintRequest.optional.default(nil) + attribute :sync, SyncRequest.optional.default(nil) + attribute :secrets, SecretsCommand.optional.default(nil) + attribute :projects, ProjectsCommand.optional.default(nil) + + def to_dynamic + { + "passwordLogin" => password_login&.to_dynamic, + "apiKeyLogin" => api_key_login&.to_dynamic, + "accessTokenLogin" => access_token_login&.to_dynamic, + "getUserApiKey" => get_user_api_key&.to_dynamic, + "fingerprint" => fingerprint&.to_dynamic, + "sync" => sync&.to_dynamic, + "secrets" => secrets&.to_dynamic, + "projects" => projects&.to_dynamic, + }.compact + end + end + + class SelectiveProjectsCommand < ProjectsCommand + attribute :get, ProjectGetRequest.optional.default(nil) + attribute :create, ProjectCreateRequest.optional.default(nil) + attribute :list, ProjectsListRequest.optional.default(nil) + attribute :update, ProjectPutRequest.optional.default(nil) + attribute :delete, ProjectsDeleteRequest.optional.default(nil) + + def to_dynamic + { + "get" => get&.to_dynamic, + "create" => create&.to_dynamic, + "list" => list&.to_dynamic, + "update" => update&.to_dynamic, + "delete" => delete&.to_dynamic, + }.compact + end + end + + class SelectiveSecretsCommand < SecretsCommand + attribute :get, SecretGetRequest.optional.default(nil) + attribute :get_by_ids, SecretsGetRequest.optional.default(nil) + attribute :create, SecretCreateRequest.optional.default(nil) + attribute :list, SecretIdentifiersRequest.optional.default(nil) + attribute :update, SecretPutRequest.optional.default(nil) + attribute :delete, SecretsDeleteRequest.optional.default(nil) + + def to_dynamic + { + "get" => get&.to_dynamic, + "getByIds" => get_by_ids&.to_dynamic, + "create" => create&.to_dynamic, + "list" => list&.to_dynamic, + "update" => update&.to_dynamic, + "delete" => delete&.to_dynamic, + }.compact + end + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/projects.rb b/languages/ruby/bitwarden_sdk_secrets/lib/projects.rb new file mode 100644 index 000000000..957a7d31d --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/projects.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require_relative 'bitwarden_error' + +module BitwardenSDKSecrets + class ProjectsClient + def initialize(command_runner) + @command_runner = command_runner + end + + def create_project(project_name, organization_id) + project_create_request = ProjectCreateRequest.new( + project_create_request_name: project_name, + organization_id: organization_id + ) + command = create_command( + create: project_create_request + ) + response = parse_response(command) + + projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') + return projects_response['data'] + end + + error_response(projects_response) + end + + def get(project_id) + project_get_request = ProjectGetRequest.new(id: project_id) + command = create_command(get: project_get_request) + response = parse_response(command) + + projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') + return projects_response['data'] + end + + error_response(projects_response) + end + + def list_projects(organization_id) + project_list_request = ProjectsListRequest.new(organization_id: organization_id) + command = create_command(list: project_list_request) + response = parse_response(command) + + projects_response = ResponseForProjectsResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') && projects_response['data'].key?('data') + return projects_response['data']['data'] + end + + error_response(projects_response) + end + + def update_project(id, project_put_request_name, organization_id) + project_put_request = ProjectPutRequest.new( + id: id, + project_put_request_name: project_put_request_name, + organization_id: organization_id + ) + command = create_command( + update: project_put_request + ) + response = parse_response(command) + + projects_response = ResponseForProjectResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') + return projects_response['data'] + end + + error_response(projects_response) + end + + def delete_projects(ids) + project_delete_request = ProjectsDeleteRequest.new(ids: ids) + command = create_command(delete: project_delete_request) + response = parse_response(command) + + projects_response = ResponseForProjectsDeleteResponse.from_json!(response).to_dynamic + + if projects_response.key?('success') && projects_response['success'] == true && + projects_response.key?('data') && projects_response['data'].key?('data') + return projects_response['data']['data'] + end + + error_response(projects_response) + end + + private + + def error_response(response) + raise BitwardenError, response['errorMessage'] if response.key?('errorMessage') + + raise BitwardenError, 'Error while getting response' + end + + def create_command(commands) + SelectiveCommand.new(projects: SelectiveProjectsCommand.new(commands)) + end + + def parse_response(command) + response = @command_runner.run(command) + raise BitwardenError, 'Error getting response' if response.nil? + + response + end + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/secrets.rb b/languages/ruby/bitwarden_sdk_secrets/lib/secrets.rb new file mode 100644 index 000000000..709d8f8b1 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/secrets.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'json' + +module BitwardenSDKSecrets + class SecretsClient + def initialize(command_runner) + @command_runner = command_runner + end + + def get(id) + command = create_command(get: SecretGetRequest.new(id: id)) + response = run_command(command) + + secrets_response = ResponseForSecretResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') + return secrets_response['data'] + end + + error_response(secrets_response) + end + + def get_by_ids(ids) + command = create_command(get_by_ids: SecretsGetRequest.new(ids: ids)) + response = run_command(command) + + secrets_response = ResponseForSecretIdentifiersResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') && secrets_response['data'].key?('data') + return secrets_response['data']['data'] + end + + error_response(secrets_response) + end + + def create(key, note, organization_id, project_ids, value) + command = create_command( + create: SecretCreateRequest.new( + key: key, note: note, organization_id: organization_id, project_ids: project_ids, value: value + ) + ) + response = run_command(command) + + secrets_response = ResponseForSecretResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') + return secrets_response['data'] + end + + error_response(secrets_response) + end + + def list(organization_id) + command = create_command(list: SecretIdentifiersRequest.new(organization_id: organization_id)) + response = run_command(command) + + secrets_response = ResponseForSecretIdentifiersResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') && secrets_response['data'].key?('data') + return secrets_response['data']['data'] + end + + error_response(secrets_response) + end + + def update(id, key, note, organization_id, project_ids, value) + command = create_command( + update: SecretPutRequest.new( + id: id, key: key, note: note, organization_id: organization_id, project_ids: project_ids, value: value + ) + ) + response = run_command(command) + + secrets_response = ResponseForSecretResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') + return secrets_response['data'] + end + + error_response(secrets_response) + end + + def delete_secret(ids) + command = create_command(delete: SecretsDeleteRequest.new(ids: ids)) + response = run_command(command) + + secrets_response = ResponseForSecretsDeleteResponse.from_json!(response).to_dynamic + + if secrets_response.key?('success') && secrets_response['success'] == true && + secrets_response.key?('data') && secrets_response['data'].key?('data') + return secrets_response['data']['data'] + end + + error_response(secrets_response) + end + + private + + def error_response(response) + if response['errorMessage'] + raise BitwardenError, response['errorMessage'] if response.key?('errorMessage') + else + raise BitwardenError, 'Error while getting response' + end + end + + def create_command(commands) + SelectiveCommand.new(secrets: SelectiveSecretsCommand.new(commands)) + end + + def run_command(command) + response = @command_runner.run(command) + raise BitwardenError, 'Error getting response' if response.nil? + + response + end + end +end diff --git a/languages/ruby/bitwarden_sdk_secrets/lib/version.rb b/languages/ruby/bitwarden_sdk_secrets/lib/version.rb new file mode 100644 index 000000000..82d5fc85b --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/lib/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module BitwardenSDKSecrets + VERSION = '0.1.0' +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk.rbs new file mode 100644 index 000000000..3f6a73f6a --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden-sdk.rbs @@ -0,0 +1,13 @@ +require_relative '../lib/schemas' + +class BitwardenClient + @command_runner: CommandRunner + + attr_reader bitwarden: Module + attr_reader project_client: ProjectsClient + attr_reader secrets_client: SecretsClient + + def initialize: (BitwardenSettings) -> void + def access_token_login: (String) -> JSON + def free_mem: () -> nil +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_settings.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_settings.rbs new file mode 100644 index 000000000..154ee16e5 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/bitwarden_settings.rbs @@ -0,0 +1,8 @@ +require_relative '../lib/schemas' + +class BitwardenSettings + attr_accessor api_url: String + attr_accessor identity_url: String + + def initialize: (String, String) -> void +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/command_runner.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/command_runner.rbs new file mode 100644 index 000000000..7a7a17dd9 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/command_runner.rbs @@ -0,0 +1,4 @@ +class CommandRunner + @bitwarden_sdk: Module + def run: -> String +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/projects_client.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/projects_client.rbs new file mode 100644 index 000000000..00c9e578d --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/projects_client.rbs @@ -0,0 +1,17 @@ +require_once '../lib/extended_schemas/schemas.rbs' +require_once '../schemas.rbs' + +class ProjectsClient + @command_runner: CommandRunner + def initialize: (command_runner: CommandRunner) -> void + def create_project: (project_name: String, organization_id: String) -> ProjectsResponse + def get: (project_id: String) -> ProjectsResponse + def list_projects: (organization_id: String) -> Array(DatumElement) + def update_project: (id: String, project_put_request_name: String, organization_id: String) -> ProjectsResponse + def delete_projects: (ids: Array[String]) -> Array(ProjectDeleteResponse) + + private + + def create_command: (SelectiveProjectsCommand) -> SelectiveCommand + def parse_response: (ResponseForProjectResponse) -> ResponseForProjectResponse +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/sdk.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/sdk.rbs new file mode 100644 index 000000000..260fa1420 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/sdk.rbs @@ -0,0 +1,3 @@ +module BitwardenSDK + VERSION: String +end diff --git a/languages/ruby/bitwarden_sdk_secrets/sig/secrets_client.rbs b/languages/ruby/bitwarden_sdk_secrets/sig/secrets_client.rbs new file mode 100644 index 000000000..ccebcecd8 --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/sig/secrets_client.rbs @@ -0,0 +1,18 @@ +require_once '../lib/extended_schemas/schemas.rbs' +require_once '../schemas.rbs' + +class SecretsClient + # @command_runner: CommandRunner + def initialize: (command_runner: CommandRunner) -> void + def get: (id: String) -> SecretResponse + def get_by_ids: (ids: Array[String]) -> Array(SecretIdentifierResponse) + def create: (key: String, note: String, organization_id: String, project_ids: Array[String], value: String) -> SecretResponse + def list: (organization_id: String) -> Array(SecretIdentifierResponse) + def update: (id: String, key: String, note: String, organization_id: String, project_ids: Array[String], value: String) -> SecretResponse + def delete_secret: (ids: Array[String]) -> Array(SecretDeleteResponse) + + private + + def create_command: (SelectiveSecretsCommand) -> SelectiveCommand + def parse_response: (SelectiveSecretCommand) -> ResponseForSecretResponse +end diff --git a/languages/ruby/bitwarden_sdk_secrets/spec/settings_spec.rb b/languages/ruby/bitwarden_sdk_secrets/spec/settings_spec.rb new file mode 100644 index 000000000..9db0e340d --- /dev/null +++ b/languages/ruby/bitwarden_sdk_secrets/spec/settings_spec.rb @@ -0,0 +1,15 @@ +require 'schemas' +require 'extended_schemas/schemas' + +describe ClientSettings do + it "test" do + client_settings = ClientSettings.new( + api_url: nil, + identity_url: nil, + user_agent: 'Bitwarden RUBY-SDK', + device_type: nil + ) + + expect(client_settings.to_dynamic.compact.to_json).to eq('{"userAgent":"Bitwarden RUBY-SDK"}') + end +end diff --git a/languages/ruby/examples/example.rb b/languages/ruby/examples/example.rb new file mode 100644 index 000000000..d1c7ce455 --- /dev/null +++ b/languages/ruby/examples/example.rb @@ -0,0 +1,69 @@ +# NOTE - for example purpose only - import gem instead +require 'bitwarden-sdk-secrets' + +token = ENV['ACCESS_TOKEN'] +organization_id = ENV['ORGANIZATION_ID'] +state_path = ENV['STATE_PATH'] + +# Configuring the URLS is optional, set them to nil to use the default values +api_url = ENV['API_URL'] +identity_url = ENV['IDENTITY_URL'] + +bitwarden_settings = BitwardenSDKSecrets::BitwardenSettings.new(api_url, identity_url) + +bw_client = BitwardenSDKSecrets::BitwardenClient.new(bitwarden_settings) +response = bw_client.access_token_login(token, state_path) +puts response + +# CREATE project +project_name = 'Test project 1' +response = bw_client.project_client.create_project(project_name, organization_id) +puts response +project_id = response['id'] + +# GET project +response = bw_client.project_client.get(project_id) +puts response + +# LIST projects +response = bw_client.project_client.list_projects(organization_id) +puts response + +# UPDATE projects +name = 'Updated test project 1' +response = bw_client.project_client.update_project(project_id, name, organization_id) +puts response + +# CREATE secret +key = 'AWS-SES' +note = 'Private account' +value = '8t27.dfj;' +response = bw_client.secrets_client.create(key, note, organization_id, [project_id], value) +puts response +secret_id = response['id'] + +# GET secret +response = bw_client.secrets_client.get(secret_id) +puts response + +# GET secret by ids +response = bw_client.secrets_client.get_by_ids([secret_id]) +puts response + +# LIST secrets +response = bw_client.secrets_client.list(organization_id) +puts response + +# UPDATE secret +note = 'updated password' +value = '7I.ert10AjK' +response = bw_client.secrets_client.update(secret_id, key, note,organization_id, [project_id], value) +puts response + +# DELETE secret +response = bw_client.secrets_client.delete_secret([secret_id]) +puts response + +# DELETE project +response = bw_client.project_client.delete_projects([project_id]) +puts response diff --git a/languages/swift/build.sh b/languages/swift/build.sh index 69da98415..69bc9a881 100755 --- a/languages/swift/build.sh +++ b/languages/swift/build.sh @@ -28,14 +28,12 @@ cargo run -p uniffi-bindgen generate \ --out-dir tmp/bindings # Move generated swift bindings -mv ./tmp/bindings/BitwardenSDK.swift ./Sources/BitwardenSdk/ -mv ./tmp/bindings/BitwardenCore.swift ./Sources/BitwardenSdk/ +mv ./tmp/bindings/*.swift ./Sources/BitwardenSdk/ # Massage the generated files to fit xcframework mkdir tmp/Headers -mv ./tmp/bindings/BitwardenFFI.h ./tmp/Headers/ -mv ./tmp/bindings/BitwardenCoreFFI.h ./tmp/Headers/ -cat ./tmp/bindings/BitwardenFFI.modulemap ./tmp/bindings/BitwardenCoreFFI.modulemap > ./tmp/Headers/module.modulemap +mv ./tmp/bindings/*.h ./tmp/Headers/ +cat ./tmp/bindings/*.modulemap > ./tmp/Headers/module.modulemap # Build xcframework xcodebuild -create-xcframework \ diff --git a/languages/swift/iOS/App.xcodeproj/project.pbxproj b/languages/swift/iOS/App.xcodeproj/project.pbxproj index 003c09c0d..e8b3519fd 100644 --- a/languages/swift/iOS/App.xcodeproj/project.pbxproj +++ b/languages/swift/iOS/App.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33F2B04F2B0511C700E1E91C /* Biometrics.swift */; }; 55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153D2A8678B300BE93F4 /* testApp.swift */; }; 55B615402A8678B300BE93F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153F2A8678B300BE93F4 /* ContentView.swift */; }; 55B615422A8678B400BE93F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 55B615412A8678B400BE93F4 /* Assets.xcassets */; }; @@ -15,6 +16,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 33F2B04F2B0511C700E1E91C /* Biometrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Biometrics.swift; sourceTree = ""; }; 55B6153A2A8678B300BE93F4 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55B6153D2A8678B300BE93F4 /* testApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testApp.swift; sourceTree = ""; }; 55B6153F2A8678B300BE93F4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -60,6 +62,7 @@ 55B6153F2A8678B300BE93F4 /* ContentView.swift */, 55B615412A8678B400BE93F4 /* Assets.xcassets */, 55B615432A8678B400BE93F4 /* Preview Content */, + 33F2B04F2B0511C700E1E91C /* Biometrics.swift */, ); path = App; sourceTree = ""; @@ -118,7 +121,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1500; TargetAttributes = { 55B615392A8678B300BE93F4 = { CreatedOnToolsVersion = 14.2; @@ -160,6 +163,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */, 55B615402A8678B300BE93F4 /* ContentView.swift in Sources */, 55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */, ); @@ -172,6 +176,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -204,6 +209,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -232,6 +238,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -264,6 +271,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -293,6 +301,7 @@ DEVELOPMENT_TEAM = LTZ2PFU5D6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -323,6 +332,7 @@ DEVELOPMENT_TEAM = LTZ2PFU5D6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/languages/swift/iOS/App/Biometrics.swift b/languages/swift/iOS/App/Biometrics.swift new file mode 100644 index 000000000..2fc5ef2a3 --- /dev/null +++ b/languages/swift/iOS/App/Biometrics.swift @@ -0,0 +1,92 @@ +// +// Biometrics.swift +// App +// +// Created by Dani on 15/11/23. +// + +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the biometric unlock functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ + +import Foundation +import LocalAuthentication + +let SERVICE: String = "com.example.app" + +// We should separate keys for each user by appending the user_id +let KEY: String = "biometric_key" + + +func biometricStoreValue(value: String) { + var error: Unmanaged? + let accessControl = SecAccessControlCreateWithFlags( + nil, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + .biometryCurrentSet, + &error) + + guard accessControl != nil && error == nil else { + fatalError("SecAccessControlCreateWithFlags failed") + } + + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: SERVICE, + kSecAttrAccount: KEY, + kSecValueData: value.data(using: .utf8)!, + kSecAttrAccessControl: accessControl as Any + ] as CFDictionary + + // Try to delete the previous secret, if it exists + // Otherwise we get `errSecDuplicateItem` + SecItemDelete(query) + + let status = SecItemAdd(query, nil) + guard status == errSecSuccess else { + fatalError("Unable to store the secret: " + errToString(status: status)) + } +} + +private func errToString(status: OSStatus) -> String { + if let err = SecCopyErrorMessageString(status, nil) as String? { + err + } else { + "Unknown error" + } +} + +func biometricRetrieveValue() -> String? { + let searchQuery = [ + kSecClass: kSecClassGenericPassword, + kSecAttrService: SERVICE, + kSecAttrAccount: KEY, + kSecMatchLimit: kSecMatchLimitOne, + kSecReturnData: true, + kSecReturnAttributes: true, + ] as CFDictionary + + var item: AnyObject? + let status = SecItemCopyMatching(searchQuery, &item) + + // If the item is not found, we just return nil + if status == errSecItemNotFound { + return nil + } + + // TODO: We probably want to handle these errors better + guard status == noErr else { + fatalError("Unable to retrieve the secret: " + errToString(status: status)) + } + + if let resultDictionary = item as? [String: Any], + let data = resultDictionary[kSecValueData as String] as? Data { + return String(decoding: data, as: UTF8.self) + } + + return nil +} diff --git a/languages/swift/iOS/App/ContentView.swift b/languages/swift/iOS/App/ContentView.swift index 4b3555403..b5fc0d4f0 100644 --- a/languages/swift/iOS/App/ContentView.swift +++ b/languages/swift/iOS/App/ContentView.swift @@ -8,170 +8,318 @@ import BitwardenSdk import SwiftUI -struct ContentView: View { - - @State private var msg: String - - init() { - let client = Client(settings: nil) +/** + * IMPORTANT: This file is provided only for the purpose of demostrating the use of the SDK functionality. + * It hasn't gone through a throrough security review and should not be considered production ready. It also doesn't + * handle a lot of errors and edge cases that a production application would need to deal with. + * Developers are encouraged to review and improve the code as needed to meet their security requirements. + * Additionally, we recommend to consult with security experts and conduct thorough testing before using the code in production. + */ - _msg = State(initialValue: client.echo(msg: "Sdk")) +let SERVER_URL = "https://localhost:8080/" +let API_URL = SERVER_URL + "api/" +let IDENTITY_URL = SERVER_URL + "identity/" - let SERVER_URL = "https://localhost:8080/" - let API_URL = SERVER_URL + "api/" - let IDENTITY_URL = SERVER_URL + "identity/" +let EMAIL = "test@bitwarden.com" +let PASSWORD = "asdfasdfasdf" - let EMAIL = "test@bitwarden.com" - let PASSWORD = "asdfasdfasdf" +let PIN = "1234" +struct ContentView: View { + private var http: URLSession + + @State private var client: Client + + @State private var accessToken: String = "" + + init() { // Disable SSL Cert validation. Don't do this in production - let ignoreHttpsDelegate = IgnoreHttpsDelegate() - let http = URLSession( - configuration: URLSessionConfiguration.default, delegate: ignoreHttpsDelegate, + http = URLSession( + configuration: URLSessionConfiguration.default, delegate: IgnoreHttpsDelegate(), delegateQueue: nil) - - Task { - - ////////////////////////////// Get master password hash ////////////////////////////// - - struct PreloginRequest: Codable { let email: String } - struct PreloginResponse: Codable { - let kdf: UInt32 - let kdfIterations: UInt32 - let kdfMemory: UInt32? - let kdfParallelism: UInt32? - - } - - let (preloginDataJson, _) = try await http.data( - for: request( - method: "POST", url: IDENTITY_URL + "accounts/prelogin", - fn: { r in - r.setValue("application/json", forHTTPHeaderField: "Content-Type") - r.httpBody = try JSONEncoder().encode(PreloginRequest(email: EMAIL)) - })) - let preloginData = try JSONDecoder().decode( - PreloginResponse.self, from: preloginDataJson) - - let kdf: Kdf - if preloginData.kdf == 0 { - kdf = Kdf.pbkdf2(iterations: preloginData.kdfIterations) - } else { - kdf = Kdf.argon2id( - iterations: preloginData.kdfIterations, memory: preloginData.kdfMemory!, - parallelism: preloginData.kdfParallelism!) - } - - let passwordHash = try await client.auth().hashPassword( - email: EMAIL, password: PASSWORD, kdfParams: kdf) - - ///////////////////////////// Login ///////////////////////////// - - struct LoginResponse: Codable { - let Key: String - let PrivateKey: String - let access_token: String - let refresh_token: String - } - - let (loginDataJson, _) = try await http.data( - for: request( - method: "POST", url: IDENTITY_URL + "connect/token", - fn: { r in - r.setValue( - EMAIL.data(using: .utf8)?.base64EncodedString(), - forHTTPHeaderField: "Auth-Email") - r.setValue( - "application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - - var comp = URLComponents() - comp.queryItems = [ - URLQueryItem(name: "scope", value: "api offline_access"), - URLQueryItem(name: "client_id", value: "web"), - URLQueryItem(name: "deviceType", value: "12"), - URLQueryItem( - name: "deviceIdentifier", - value: "0745d426-8dab-484a-9816-4959721d77c7"), - URLQueryItem(name: "deviceName", value: "edge"), - URLQueryItem(name: "grant_type", value: "password"), - URLQueryItem(name: "username", value: EMAIL), - URLQueryItem(name: "password", value: passwordHash), - ] - r.httpBody = comp.percentEncodedQuery! - .replacingOccurrences(of: "@", with: "%40") - .replacingOccurrences(of: "+", with: "%2B") - .data(using: .utf8) - })) - let loginData = try JSONDecoder().decode(LoginResponse.self, from: loginDataJson) - - ///////////////////////////// Sync ///////////////////////////// - - struct SyncOrganization: Codable { - let id: String - let key: String - } - struct SyncProfile: Codable { - let organizations: [SyncOrganization] - - } - struct SyncFolder: Codable { - let id: String - let name: String - let revisionDate: String - } - struct SyncResponse: Codable { - let profile: SyncProfile - let folders: [SyncFolder] - } - - let (syncDataJson, _) = try await http.data( - for: request( - method: "GET", url: API_URL + "sync?excludeDomains=true", - fn: { r in - r.setValue( - "Bearer " + loginData.access_token, forHTTPHeaderField: "Authorization") - })) - - let syncData = try JSONDecoder().decode(SyncResponse.self, from: syncDataJson) - - ///////////////////////////// Initialize crypto ///////////////////////////// - - try await client.crypto().initializeCrypto( - req: InitCryptoRequest( - kdfParams: kdf, - email: EMAIL, - password: PASSWORD, - userKey: loginData.Key, - privateKey: loginData.PrivateKey, - organizationKeys: Dictionary.init( - uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } - ) - )) - - ///////////////////////////// Decrypt some folders ///////////////////////////// - - let dateFormatter = ISO8601DateFormatter() - dateFormatter.formatOptions = [.withFractionalSeconds] - - let decryptedFolders = try await client.vault().folders().decryptList( - folders: syncData.folders.map { - Folder( - id: $0.id, name: $0.name, - revisionDate: dateFormatter.date(from: $0.revisionDate)!) - }) - print(decryptedFolders) - } + + client = Client(settings: nil) } - + + @State var setupBiometrics: Bool = true + @State var setupPin: Bool = true + @State var outputText: String = "" + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundColor(.accentColor) - Text("Hello " + msg) + Toggle("Setup biometric unlock after login", isOn: $setupBiometrics).padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20)) + Toggle("Setup PIN unlock after login", isOn: $setupPin).padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20)) + + Button(action: { + Task { + do { + try await clientExamplePassword(clientAuth: client.auth(), clientCrypto: client.crypto(), setupBiometrics: setupBiometrics, setupPin: setupPin) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Login with username + password") + }) + + Divider().padding(30) + + Button(action: { + Task { + do { + try await clientExampleBiometrics(clientCrypto: client.crypto()) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Unlock with biometrics") + }) + + Button(action: { + Task { + do { + try await clientExamplePin(clientCrypto: client.crypto()) + try await decryptVault(clientCrypto: client.crypto(), clientVault: client.vault()) + } catch { + print("ERROR:", error) + } + } + }, label: { + Text("Unlock with PIN") + }) + + Button(action: { + client = Client(settings: nil) + }, label: { + Text("Lock & reset client") + }).padding() + + Text("Output: " + outputText).padding(.top) } .padding() } + + func clientExamplePassword(clientAuth: ClientAuthProtocol, clientCrypto: ClientCryptoProtocol, setupBiometrics: Bool, setupPin: Bool) async throws { + ////////////////////////////// Get master password hash ////////////////////////////// + + struct PreloginRequest: Codable { let email: String } + struct PreloginResponse: Codable { + let kdf: UInt32 + let kdfIterations: UInt32 + let kdfMemory: UInt32? + let kdfParallelism: UInt32? + + } + + let (preloginDataJson, _) = try await http.data( + for: request( + method: "POST", url: IDENTITY_URL + "accounts/prelogin", + fn: { r in + r.setValue("application/json", forHTTPHeaderField: "Content-Type") + r.httpBody = try JSONEncoder().encode(PreloginRequest(email: EMAIL)) + })) + let preloginData = try JSONDecoder().decode( + PreloginResponse.self, from: preloginDataJson) + + let kdf: Kdf + if preloginData.kdf == 0 { + kdf = Kdf.pbkdf2(iterations: preloginData.kdfIterations) + } else { + kdf = Kdf.argon2id( + iterations: preloginData.kdfIterations, memory: preloginData.kdfMemory!, + parallelism: preloginData.kdfParallelism!) + } + + let passwordHash = try await clientAuth.hashPassword( + email: EMAIL, password: PASSWORD, kdfParams: kdf, purpose: .serverAuthorization) + + ///////////////////////////// Login ///////////////////////////// + + struct LoginResponse: Codable { + let Key: String + let PrivateKey: String + let access_token: String + let refresh_token: String + } + + let (loginDataJson, _) = try await http.data( + for: request( + method: "POST", url: IDENTITY_URL + "connect/token", + fn: { r in + r.setValue( + EMAIL.data(using: .utf8)?.base64EncodedString(), + forHTTPHeaderField: "Auth-Email") + r.setValue( + "application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + var comp = URLComponents() + comp.queryItems = [ + URLQueryItem(name: "scope", value: "api offline_access"), + URLQueryItem(name: "client_id", value: "web"), + URLQueryItem(name: "deviceType", value: "12"), + URLQueryItem( + name: "deviceIdentifier", + value: "0745d426-8dab-484a-9816-4959721d77c7"), + URLQueryItem(name: "deviceName", value: "edge"), + URLQueryItem(name: "grant_type", value: "password"), + URLQueryItem(name: "username", value: EMAIL), + URLQueryItem(name: "password", value: passwordHash), + ] + r.httpBody = comp.percentEncodedQuery! + .replacingOccurrences(of: "@", with: "%40") + .replacingOccurrences(of: "+", with: "%2B") + .data(using: .utf8) + })) + let loginData = try JSONDecoder().decode(LoginResponse.self, from: loginDataJson) + + try await clientCrypto.initializeUserCrypto( + req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: loginData.PrivateKey, + method: InitUserCryptoMethod.password( + password: PASSWORD, + userKey: loginData.Key + ) + )) + + accessToken = loginData.access_token + + if (setupBiometrics) { + let defaults = UserDefaults.standard + defaults.set(loginData.PrivateKey, forKey: "privateKey") + defaults.set(preloginData.kdf, forKey: "kdfType") + defaults.set(preloginData.kdfIterations, forKey: "kdfIterations") + defaults.set(preloginData.kdfMemory, forKey: "kdfMemory") + defaults.set(preloginData.kdfParallelism, forKey: "kdfParallelism") + defaults.synchronize() + + let key = try await clientCrypto.getUserEncryptionKey() + biometricStoreValue(value: key) + } + + if (setupPin) { + let pinOptions = try await clientCrypto.derivePinKey(pin: PIN) + + let defaults = UserDefaults.standard + defaults.set(loginData.PrivateKey, forKey: "privateKey") + defaults.set(preloginData.kdf, forKey: "kdfType") + defaults.set(preloginData.kdfIterations, forKey: "kdfIterations") + defaults.set(preloginData.kdfMemory, forKey: "kdfMemory") + defaults.set(preloginData.kdfParallelism, forKey: "kdfParallelism") + + defaults.set(pinOptions.encryptedPin, forKey: "encryptedPin") + defaults.set(pinOptions.pinProtectedUserKey, forKey: "pinProtectedUserKey") + + defaults.synchronize() + } + } + + func clientExampleBiometrics(clientCrypto: ClientCryptoProtocol) async throws { + let defaults = UserDefaults.standard + let privateKey = defaults.string(forKey: "privateKey")! + let kdf = if defaults.integer(forKey: "kdfType") == 0 { + Kdf.pbkdf2(iterations: UInt32(defaults.integer(forKey: "kdfIterations"))) + } else { + Kdf.argon2id( + iterations: UInt32(defaults.integer(forKey: "kdfIterations")), + memory: UInt32(defaults.integer(forKey: "kdfMemory")), + parallelism: UInt32(defaults.integer(forKey: "kdfParallelism")) + ) + } + + let key = biometricRetrieveValue()! + + try await clientCrypto.initializeUserCrypto(req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: privateKey, + method: InitUserCryptoMethod.decryptedKey( + decryptedUserKey: key + ) + )) + } + + func clientExamplePin(clientCrypto: ClientCryptoProtocol) async throws { + let defaults = UserDefaults.standard + let privateKey = defaults.string(forKey: "privateKey")! + let kdf = if defaults.integer(forKey: "kdfType") == 0 { + Kdf.pbkdf2(iterations: UInt32(defaults.integer(forKey: "kdfIterations"))) + } else { + Kdf.argon2id( + iterations: UInt32(defaults.integer(forKey: "kdfIterations")), + memory: UInt32(defaults.integer(forKey: "kdfMemory")), + parallelism: UInt32(defaults.integer(forKey: "kdfParallelism")) + ) + } + + let encryptedPin = defaults.string(forKey: "encryptedPin")! + let pinProtectedUserKey = defaults.string(forKey: "pinProtectedUserKey")! + + try await clientCrypto.initializeUserCrypto(req: InitUserCryptoRequest( + kdfParams: kdf, + email: EMAIL, + privateKey: privateKey, + method: InitUserCryptoMethod.pin(pin: PIN, pinProtectedUserKey: pinProtectedUserKey) + )) + } + + func decryptVault(clientCrypto: ClientCryptoProtocol, clientVault: ClientVaultProtocol) async throws { + ///////////////////////////// Sync ///////////////////////////// + + struct SyncOrganization: Codable { + let id: String + let key: String + } + struct SyncProfile: Codable { + let organizations: [SyncOrganization] + + } + struct SyncFolder: Codable { + let id: String + let name: String + let revisionDate: String + } + struct SyncResponse: Codable { + let profile: SyncProfile + let folders: [SyncFolder] + } + + let (syncDataJson, _) = try await http.data( + for: request( + method: "GET", url: API_URL + "sync?excludeDomains=true", + fn: { r in + r.setValue( + "Bearer " + accessToken, forHTTPHeaderField: "Authorization") + })) + + let syncData = try JSONDecoder().decode(SyncResponse.self, from: syncDataJson) + + ///////////////////////////// Initialize org crypto ///////////////////////////// + + try await clientCrypto.initializeOrgCrypto( + req: InitOrgCryptoRequest( + organizationKeys: Dictionary.init( + uniqueKeysWithValues: syncData.profile.organizations.map { ($0.id, $0.key) } + ) + )) + + ///////////////////////////// Decrypt some folders ///////////////////////////// + + let dateFormatter = ISO8601DateFormatter() + dateFormatter.formatOptions = [.withFractionalSeconds] + + let decryptedFolders = try await clientVault.folders().decryptList( + folders: syncData.folders.map { + Folder( + id: $0.id, name: $0.name, + revisionDate: dateFormatter.date(from: $0.revisionDate)!) + }) + print(decryptedFolders) + } } struct ContentView_Previews: PreviewProvider { @@ -196,7 +344,7 @@ extension IgnoreHttpsDelegate: URLSessionDelegate { ) { //Trust the certificate even if not valid let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!) - + completionHandler(.useCredential, urlCredential) } } diff --git a/openapitools.json b/openapitools.json index a3883a34f..e73b97583 100644 --- a/openapitools.json +++ b/openapitools.json @@ -2,6 +2,6 @@ "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", "spaces": 2, "generator-cli": { - "version": "6.5.0" + "version": "7.2.0" } } diff --git a/package-lock.json b/package-lock.json index 22f0e6282..5728464b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,19 @@ "version": "0.0.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { - "@openapitools/openapi-generator-cli": "2.7.0", + "@openapitools/openapi-generator-cli": "2.9.0", "handlebars": "^4.7.8", - "prettier": "3.0.3", - "quicktype-core": "23.0.76", + "prettier": "3.2.5", + "quicktype-core": "23.0.81", "rimraf": "5.0.5", - "ts-node": "10.9.1", - "typescript": "5.2.2" + "ts-node": "10.9.2", + "typescript": "5.3.3" } }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -145,9 +145,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "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" @@ -179,44 +179,38 @@ } }, "node_modules/@nestjs/axios": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.0.tgz", - "integrity": "sha512-b2TT2X6BFbnNoeteiaxCIiHaFcSbVW+S5yygYqiIq5i6H77yIU3IVuLdpQkHq8/EqOWFwMopLN8jdkUT71Am9w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz", + "integrity": "sha512-VlOZhAGDmOoFdsmewn8AyClAdGpKXQQaY1+3PGB+g6ceurGIdTxZgRX3VXc1T6Zs60PedWjg3A82TDOB05mrzQ==", "dev": true, - "dependencies": { - "axios": "0.27.2" - }, "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "axios": "^1.3.1", "reflect-metadata": "^0.1.12", "rxjs": "^6.0.0 || ^7.0.0" } }, "node_modules/@nestjs/common": { - "version": "9.3.11", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.3.11.tgz", - "integrity": "sha512-IFZ2G/5UKWC2Uo7tJ4SxGed2+aiA+sJyWeWsGTogKVDhq90oxVBToh+uCDeI31HNUpqYGoWmkletfty42zUd8A==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.0.tgz", + "integrity": "sha512-DGv34UHsZBxCM3H5QGE2XE/+oLJzz5+714JQjBhjD9VccFlQs3LRxo/epso4l7nJIiNlZkPyIUC8WzfU/5RTsQ==", "dev": true, "dependencies": { "iterare": "1.2.1", - "tslib": "2.5.0", - "uid": "2.0.1" + "tslib": "2.6.2", + "uid": "2.0.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "cache-manager": "<=5", "class-transformer": "*", "class-validator": "*", "reflect-metadata": "^0.1.12", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { - "cache-manager": { - "optional": true - }, "class-transformer": { "optional": true }, @@ -225,16 +219,10 @@ } } }, - "node_modules/@nestjs/common/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, "node_modules/@nestjs/core": { - "version": "9.3.11", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.3.11.tgz", - "integrity": "sha512-CI27a2JFd5rvvbgkalWqsiwQNhcP4EAG5BUK8usjp29wVp1kx30ghfBT8FLqIgmkRVo65A0IcEnWsxeXMntkxQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.0.tgz", + "integrity": "sha512-N06P5ncknW/Pm8bj964WvLIZn2gNhHliCBoAO1LeBvNImYkecqKcrmLbY49Fa1rmMfEM3MuBHeDys3edeuYAOA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -242,18 +230,18 @@ "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "3.2.0", - "tslib": "2.5.0", - "uid": "2.0.1" + "tslib": "2.6.2", + "uid": "2.0.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/microservices": "^9.0.0", - "@nestjs/platform-express": "^9.0.0", - "@nestjs/websockets": "^9.0.0", + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", "reflect-metadata": "^0.1.12", "rxjs": "^7.1.0" }, @@ -269,12 +257,6 @@ } } }, - "node_modules/@nestjs/core/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, "node_modules/@nuxtjs/opencollective": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", @@ -294,28 +276,29 @@ } }, "node_modules/@openapitools/openapi-generator-cli": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.7.0.tgz", - "integrity": "sha512-ieEpHTA/KsDz7ANw03lLPYyjdedDEXYEyYoGBRWdduqXWSX65CJtttjqa8ZaB1mNmIjMtchUHwAYQmTLVQ8HYg==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.9.0.tgz", + "integrity": "sha512-KQpftKeiMoH5aEI/amOVLFGkGeT3DyA7Atj7v7l8xT3O9xnIUpoDmMg0WBTEh+NHxEwEAITQNDzr+JLjkXVaKw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@nestjs/axios": "0.1.0", - "@nestjs/common": "9.3.11", - "@nestjs/core": "9.3.11", + "@nestjs/axios": "3.0.1", + "@nestjs/common": "10.3.0", + "@nestjs/core": "10.3.0", "@nuxtjs/opencollective": "0.3.2", + "axios": "1.6.5", "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.1.6", - "inquirer": "8.2.5", + "glob": "7.2.3", + "inquirer": "8.2.6", "lodash": "4.17.21", "reflect-metadata": "0.1.13", - "rxjs": "7.8.0", - "tslib": "2.0.3" + "rxjs": "7.8.1", + "tslib": "2.6.2" }, "bin": { "openapi-generator-cli": "main.js" @@ -363,16 +346,19 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.6.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.5.tgz", - "integrity": "sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dev": true, - "peer": true + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/urijs": { - "version": "1.19.20", - "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.20.tgz", - "integrity": "sha512-77Mq/2BeHU894J364dUv9tSwxxyCLtcX228Pc8TwZpP5bvOoMns+gZoftp3LYl3FBH8vChpWbuagKGiMki2c1A==", + "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": { @@ -388,9 +374,9 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "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" @@ -400,9 +386,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "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" @@ -460,13 +446,14 @@ "dev": true }, "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dev": true, "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/balanced-match": { @@ -581,9 +568,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -612,6 +599,23 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", @@ -848,9 +852,9 @@ "dev": true }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -919,9 +923,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -1010,15 +1014,15 @@ } }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -1114,9 +1118,9 @@ "dev": true }, "node_modules/inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", @@ -1133,7 +1137,7 @@ "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^6.0.1" }, "engines": { "node": ">=12.0.0" @@ -1209,9 +1213,9 @@ } }, "node_modules/js-base64": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", - "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==", + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.6.tgz", + "integrity": "sha512-NPrWuHFxFUknr1KqJRDgUQPexQF0uIJWjeT+2KjEePhitQxQEx5EJBG1lVn5/hc8aLycTpXrDOgPQ6Zq+EDiTA==", "dev": true }, "node_modules/jsonfile": { @@ -1249,9 +1253,9 @@ } }, "node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -1315,9 +1319,9 @@ } }, "node_modules/minipass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", - "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -1467,9 +1471,9 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -1490,10 +1494,16 @@ "node": ">= 0.6.0" } }, + "node_modules/proxy-from-env": { + "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 + }, "node_modules/quicktype-core": { - "version": "23.0.76", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.76.tgz", - "integrity": "sha512-QinZRNovSTQcFuhRKxeHb22eFmyucbG96EPaQDSbz9qvIPxUhs1BZviNc8HAkHWYFqTSET/xZcEoHpm1DeDbRg==", + "version": "23.0.81", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.81.tgz", + "integrity": "sha512-iJQpCEzSQIkffJPS5NC+0w+Rq9faGgz09L+WIbseu1toFfj+M/3KTG5jhzdY/uN88fWosAom2fMoEADA403+rQ==", "dev": true, "dependencies": { "@glideapps/ts-necessities": "2.1.3", @@ -1574,9 +1584,9 @@ "dev": true }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/require-directory": { @@ -1675,20 +1685,14 @@ } }, "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1872,9 +1876,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -1915,9 +1919,9 @@ } }, "node_modules/tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, "node_modules/type-fest": { @@ -1933,9 +1937,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -1959,9 +1963,9 @@ } }, "node_modules/uid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.1.tgz", - "integrity": "sha512-PF+1AnZgycpAIEmNtjxGBVmKbZAQguaa4pBUq6KNaGEcpzZ2klCNZLM34tsjp76maN00TttiiUf6zkIBpJQm2A==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", "dev": true, "dependencies": { "@lukeed/csprng": "^1.0.0" @@ -1970,6 +1974,13 @@ "node": ">=8" } }, + "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/unicode-properties": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", @@ -1997,9 +2008,9 @@ "dev": true }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -2070,9 +2081,9 @@ "dev": true }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "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, "dependencies": { "ansi-styles": "^4.0.0", @@ -2080,10 +2091,7 @@ "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=8" } }, "node_modules/wrap-ansi-cjs": { @@ -2120,9 +2128,9 @@ } }, "node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "dev": true, "engines": { "node": ">= 14" diff --git a/package.json b/package.json index fdeb8fe58..05195270e 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,12 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { - "@openapitools/openapi-generator-cli": "2.7.0", + "@openapitools/openapi-generator-cli": "2.9.0", "handlebars": "^4.7.8", - "prettier": "3.0.3", - "quicktype-core": "23.0.76", + "prettier": "3.2.5", + "quicktype-core": "23.0.81", "rimraf": "5.0.5", - "ts-node": "10.9.1", - "typescript": "5.2.2" + "ts-node": "10.9.2", + "typescript": "5.3.3" } } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..bb3baeccd --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +# Wrap comments and increase the width of comments to 100 +comment_width = 100 +wrap_comments = true + +# Sort and group imports +group_imports = "StdExternalCrate" +imports_granularity = "Crate" diff --git a/sig/bitwarden_sdk/bitwarden_client.rbs b/sig/bitwarden_sdk/bitwarden_client.rbs new file mode 100644 index 000000000..1ff97eb92 --- /dev/null +++ b/sig/bitwarden_sdk/bitwarden_client.rbs @@ -0,0 +1,5 @@ +module BitwardenSDK + class BitwardenClient + attr_reader project_client: ProjectsClient + end +end diff --git a/support/build-api.sh b/support/build-api.sh new file mode 100755 index 000000000..ea295a523 --- /dev/null +++ b/support/build-api.sh @@ -0,0 +1,27 @@ +# Delete old directory to ensure all files are updated +rm -rf crates/bitwarden-api-api/src + +# Generate new API bindings +npx openapi-generator-cli generate \ + -i ../server/api.json \ + -g rust \ + -o crates/bitwarden-api-api \ + --package-name bitwarden-api-api \ + -t ./support/openapi-template \ + --additional-properties=packageVersion=1.0.0 + +# Delete old directory to ensure all files are updated +rm -rf crates/bitwarden-api-identity/src + +# Generate new Identity bindings +npx openapi-generator-cli generate \ + -i ../server/identity.json \ + -g rust \ + -o crates/bitwarden-api-identity \ + --package-name bitwarden-api-identity \ + -t ./support/openapi-template \ + --additional-properties=packageVersion=1.0.0 + +rustup toolchain install nightly +cargo +nightly fmt +npm run prettier diff --git a/support/docs/docs.ts b/support/docs/docs.ts index 14603dd57..067ff0827 100644 --- a/support/docs/docs.ts +++ b/support/docs/docs.ts @@ -21,6 +21,7 @@ const template = Handlebars.compile( const rootElements = [ "Client", "ClientAuth", + "ClientAttachments", "ClientCiphers", "ClientCollections", "ClientCrypto", @@ -28,6 +29,7 @@ const rootElements = [ "ClientFolders", "ClientGenerators", "ClientPasswordHistory", + "ClientPlatform", "ClientSends", "ClientVault", ]; diff --git a/support/openapi-template/Cargo.mustache b/support/openapi-template/Cargo.mustache index 38885cb6f..08f1a942e 100644 --- a/support/openapi-template/Cargo.mustache +++ b/support/openapi-template/Cargo.mustache @@ -1,41 +1,71 @@ [package] name = "{{{packageName}}}" version = "{{#lambdaVersion}}{{{packageVersion}}}{{/lambdaVersion}}" +{{#infoEmail}} +authors = ["{{{.}}}"] +{{/infoEmail}} +{{^infoEmail}} authors = ["OpenAPI Generator team and contributors"] +{{/infoEmail}} +{{#appDescription}} +description = "{{{.}}}" +{{/appDescription}} +{{#licenseInfo}} +license = "{{.}}" +{{/licenseInfo}} +{{^licenseInfo}} +# Override this license by providing a License Object in the OpenAPI. +license = "Unlicense" +{{/licenseInfo}} edition = "2018" +{{#publishRustRegistry}} +publish = ["{{.}}"] +{{/publishRustRegistry}} +{{#repositoryUrl}} +repository = "{{.}}" +{{/repositoryUrl}} +{{#documentationUrl}} +documentation = "{{.}}" +{{/documentationUrl}} +{{#homePageUrl}} +homepage = "{{.}} +{{/homePageUrl}} [dependencies] serde = "^1.0" serde_derive = "^1.0" +{{#serdeWith}} +serde_with = "^2.0" +{{/serdeWith}} serde_json = "^1.0" serde_repr = "^0.1" url = "^2.2" -uuid = { version = "^1.0", features = ["serde"] } +uuid = { version = "^1.0", features = ["serde", "v4"] } {{#hyper}} hyper = { version = "~0.14", features = ["full"] } hyper-tls = "~0.5" http = "~0.2" -serde_yaml = "0.7" base64 = "~0.7.0" futures = "^0.3" {{/hyper}} +{{#withAWSV4Signature}} +aws-sigv4 = "0.3.0" +http = "0.2.5" +secrecy = "0.8.0" +{{/withAWSV4Signature}} {{#reqwest}} {{^supportAsync}} -reqwest = "~0.9" +[dependencies.reqwest] +version = "^0.11" +features = ["json", "blocking", "multipart"] {{/supportAsync}} {{#supportAsync}} +{{#supportMiddleware}} +reqwest-middleware = "0.2.0" +{{/supportMiddleware}} [dependencies.reqwest] version = "^0.11" -features = ["json", "multipart"] +features = ["http2", "json", "multipart"] +default-features = false {{/supportAsync}} {{/reqwest}} -{{#withAWSV4Signature}} -aws-sigv4 = "0.3.0" -http = "0.2.5" -secrecy = "0.8.0" -{{/withAWSV4Signature}} - -[dev-dependencies] -{{#hyper}} -tokio-core = "*" -{{/hyper}} diff --git a/support/openapi-template/git_push.sh.mustache b/support/openapi-template/git_push.sh.mustache old mode 100644 new mode 100755 diff --git a/support/openapi-template/hyper/api.mustache b/support/openapi-template/hyper/api.mustache index 03ea90ad0..dffab3ce8 100644 --- a/support/openapi-template/hyper/api.mustache +++ b/support/openapi-template/hyper/api.mustache @@ -28,7 +28,7 @@ impl {{{classname}}}Client pub trait {{{classname}}} { {{#operations}} {{#operation}} - fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}&str{{/isString}}{{#isUuid}}uuid::Uuid{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin>>>; + fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin>>>; {{/operation}} {{/operations}} } @@ -38,7 +38,7 @@ impl{{{classname}}} for {{{classname}}}Clien {{#operations}} {{#operation}} #[allow(unused_mut)] - fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}&str{{/isString}}{{#isUuid}}uuid::Uuid{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin>>> { + fn {{{operationId}}}(&self, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{^isUuid}}&str{{/isUuid}}{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Pin>>> { let mut req = __internal_request::Request::new(hyper::Method::{{{httpMethod.toUpperCase}}}, "{{{path}}}".to_string()) {{#hasAuthMethods}} {{#authMethods}} @@ -49,9 +49,9 @@ impl{{{classname}}} for {{{classname}}}Clien param_name: "{{{keyParamName}}}".to_owned(), })) {{/isApiKey}} - {{#isBasic}} + {{#isBasicBasic}} .with_auth(__internal_request::Auth::Basic) - {{/isBasic}} + {{/isBasicBasic}} {{#isOAuth}} .with_auth(__internal_request::Auth::Oauth) {{/isOAuth}} @@ -66,7 +66,7 @@ impl{{{classname}}} for {{{classname}}}Clien {{#isNullable}} match {{{paramName}}} { Some(param_value) => { req = req.with_query_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, - None => { req = req.with_query_param("{{{baseName}}}".to_string(), String::new()); }, + None => { req = req.with_query_param("{{{baseName}}}".to_string(), "".to_string()); }, } {{/isNullable}} {{/required}} @@ -85,7 +85,7 @@ impl{{{classname}}} for {{{classname}}}Clien {{#isNullable}} match {{{paramName}}} { Some(param_value) => { req = req.with_path_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, - None => { req = req.with_path_param("{{{baseName}}}".to_string(), String::new()); }, + None => { req = req.with_path_param("{{{baseName}}}".to_string(), "".to_string()); }, } {{/isNullable}} {{/required}} @@ -104,7 +104,7 @@ impl{{{classname}}} for {{{classname}}}Clien {{#isNullable}} match {{{paramName}}} { Some(param_value) => { req = req.with_header_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, - None => { req = req.with_header_param("{{{baseName}}}".to_string(), String::new()); }, + None => { req = req.with_header_param("{{{baseName}}}".to_string(), "".to_string()); }, } {{/isNullable}} {{/required}} @@ -143,7 +143,7 @@ impl{{{classname}}} for {{{classname}}}Clien {{#isNullable}} match {{{paramName}}} { Some(param_value) => { req = req.with_form_param("{{{baseName}}}".to_string(), param_value{{#isArray}}.join(","){{/isArray}}.to_string()); }, - None => { req = req.with_form_param("{{{baseName}}}".to_string(), String::new()); }, + None => { req = req.with_form_param("{{{baseName}}}".to_string(), "".to_string()); }, } {{/isNullable}} {{/required}} diff --git a/support/openapi-template/model.mustache b/support/openapi-template/model.mustache index 233c88010..4a22118a8 100644 --- a/support/openapi-template/model.mustache +++ b/support/openapi-template/model.mustache @@ -100,7 +100,7 @@ pub enum {{{classname}}} { {{!-- for non-enum schemas --}} {{^isEnum}} {{^discriminator}} -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct {{{classname}}} { {{#vars}} {{#description}} diff --git a/support/openapi-template/request.rs b/support/openapi-template/request.rs index 6e534abaa..81497706a 100644 --- a/support/openapi-template/request.rs +++ b/support/openapi-template/request.rs @@ -2,10 +2,10 @@ use std::collections::HashMap; use std::pin::Pin; use futures; -use futures::future::*; use futures::Future; +use futures::future::*; use hyper; -use hyper::header::{HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT}; +use hyper::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, HeaderValue, USER_AGENT}; use serde; use serde_json; @@ -50,6 +50,7 @@ pub(crate) struct Request { serialized_body: Option, } +#[allow(dead_code)] impl Request { pub fn new(method: hyper::Method, path: String) -> Self { Request { @@ -75,16 +76,19 @@ impl Request { self } + #[allow(unused)] pub fn with_query_param(mut self, basename: String, param: String) -> Self { self.query_params.insert(basename, param); self } + #[allow(unused)] pub fn with_path_param(mut self, basename: String, param: String) -> Self { self.path_params.insert(basename, param); self } + #[allow(unused)] pub fn with_form_param(mut self, basename: String, param: String) -> Self { self.form_params.insert(basename, param); self @@ -103,13 +107,13 @@ impl Request { pub fn execute<'a, C, U>( self, conf: &configuration::Configuration, - ) -> Pin> + 'a>> - where - C: hyper::client::connect::Connect + Clone + std::marker::Send + Sync, - U: Sized + std::marker::Send + 'a, - for<'de> U: serde::Deserialize<'de>, + ) -> Pin> + 'a>> + where + C: hyper::client::connect::Connect + Clone + std::marker::Send + Sync, + U: Sized + std::marker::Send + 'a, + for<'de> U: serde::Deserialize<'de>, { - let mut query_string = ::url::form_urlencoded::Serializer::new(String::new()); + let mut query_string = ::url::form_urlencoded::Serializer::new("".to_owned()); let mut path = self.path; for (k, v) in self.path_params { @@ -133,7 +137,9 @@ impl Request { Ok(u) => u, }; - let mut req_builder = hyper::Request::builder().uri(uri).method(self.method); + let mut req_builder = hyper::Request::builder() + .uri(uri) + .method(self.method); // Detect the authorization type if it hasn't been set. let auth = self.auth.unwrap_or_else(|| @@ -180,13 +186,10 @@ impl Request { } if let Some(ref user_agent) = conf.user_agent { - req_builder = req_builder.header( - USER_AGENT, - match HeaderValue::from_str(user_agent) { - Ok(header_value) => header_value, - Err(e) => return Box::pin(futures::future::err(super::Error::Header(e))), - }, - ); + req_builder = req_builder.header(USER_AGENT, match HeaderValue::from_str(user_agent) { + Ok(header_value) => header_value, + Err(e) => return Box::pin(futures::future::err(super::Error::Header(e))) + }); } for (k, v) in self.header_params { @@ -195,11 +198,8 @@ impl Request { let req_headers = req_builder.headers_mut().unwrap(); let request_result = if self.form_params.len() > 0 { - req_headers.insert( - CONTENT_TYPE, - HeaderValue::from_static("application/ x-www-form-urlencoded"), - ); - let mut enc = ::url::form_urlencoded::Serializer::new(String::new()); + req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded")); + let mut enc = ::url::form_urlencoded::Serializer::new("".to_owned()); for (k, v) in self.form_params { enc.append_pair(&k, &v); } @@ -213,37 +213,30 @@ impl Request { }; let request = match request_result { Ok(request) => request, - Err(e) => return Box::pin(futures::future::err(Error::from(e))), + Err(e) => return Box::pin(futures::future::err(Error::from(e))) }; let no_return_type = self.no_return_type; - Box::pin( - conf.client - .request(request) - .map_err(Error::from) - .and_then(move |response| { - let status = response.status(); - if !status.is_success() { - futures::future::err::(Error::from(( - status, - response.into_body(), - ))) - .boxed() - } else if no_return_type { - // This is a hack; if there's no_ret_type, U is (), but serde_json gives an - // error when deserializing "" into (), so deserialize 'null' into it - // instead. - // An alternate option would be to require U: Default, and then return - // U::default() here instead since () implements that, but then we'd - // need to impl default for all models. - futures::future::ok::(serde_json::Value::Null).boxed() - } else { - hyper::body::to_bytes(response.into_body()) - .map(|bytes| serde_json::from_slice(&bytes.unwrap())) - .map_err(Error::from) - .boxed() - } - }), - ) + Box::pin(conf.client + .request(request) + .map_err(|e| Error::from(e)) + .and_then(move |response| { + let status = response.status(); + if !status.is_success() { + futures::future::err::(Error::from((status, response.into_body()))).boxed() + } else if no_return_type { + // This is a hack; if there's no_ret_type, U is (), but serde_json gives an + // error when deserializing "" into (), so deserialize 'null' into it + // instead. + // An alternate option would be to require U: Default, and then return + // U::default() here instead since () implements that, but then we'd + // need to impl default for all models. + futures::future::ok::(serde_json::from_str("null").expect("serde null value")).boxed() + } else { + hyper::body::to_bytes(response.into_body()) + .map(|bytes| serde_json::from_slice(&bytes.unwrap())) + .map_err(|e| Error::from(e)).boxed() + } + })) } } diff --git a/support/openapi-template/reqwest/api.mustache b/support/openapi-template/reqwest/api.mustache index b1a2ec245..8128244a2 100644 --- a/support/openapi-template/reqwest/api.mustache +++ b/support/openapi-template/reqwest/api.mustache @@ -11,13 +11,13 @@ use super::{Error, configuration}; {{#allParams}} {{#-first}} /// struct for passing parameters to the method [`{{operationId}}`] -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct {{{operationIdCamelCase}}}Params { {{/-first}} {{#description}} /// {{{.}}} {{/description}} - pub {{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isString}}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}uuid::Uuid{{#isArray}}>{{/isArray}}{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}crate::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}},{{/-last}} + pub {{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{^isUuid}}{{#isString}}{{#isArray}}Vec<{{/isArray}}String{{#isArray}}>{{/isArray}}{{/isString}}{{/isUuid}}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}uuid::Uuid{{#isArray}}>{{/isArray}}{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}{{#isBodyParam}}crate::models::{{/isBodyParam}}{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}},{{/-last}} {{#-last}} } @@ -103,12 +103,27 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: {{#required}} {{#isArray}} local_var_req_builder = match "{{collectionFormat}}" { - "multi" => local_var_req_builder.query(&{{{paramName}}}.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p)).collect::>()), + "multi" => local_var_req_builder.query(&{{{paramName}}}.into_iter().map(|p| ("{{{baseName}}}".to_owned(), p.to_string())).collect::>()), _ => local_var_req_builder.query(&[("{{{baseName}}}", &{{{paramName}}}.into_iter().map(|p| p.to_string()).collect::>().join(",").to_string())]), }; {{/isArray}} {{^isArray}} + {{^isNullable}} local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &{{{paramName}}}.to_string())]); + {{/isNullable}} + {{#isNullable}} + {{#isDeepObject}} + if let Some(ref local_var_str) = {{{paramName}}} { + let params = crate::apis::parse_deep_object("{{{baseName}}}", local_var_str); + local_var_req_builder = local_var_req_builder.query(¶ms); + }; + {{/isDeepObject}} + {{^isDeepObject}} + if let Some(ref local_var_str) = {{{paramName}}} { + local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &local_var_str.to_string())]); + }; + {{/isDeepObject}} + {{/isNullable}} {{/isArray}} {{/required}} {{^required}} @@ -120,7 +135,13 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: }; {{/isArray}} {{^isArray}} + {{#isDeepObject}} + let params = crate::apis::parse_deep_object("{{{baseName}}}", local_var_str); + local_var_req_builder = local_var_req_builder.query(¶ms); + {{/isDeepObject}} + {{^isDeepObject}} local_var_req_builder = local_var_req_builder.query(&[("{{{baseName}}}", &local_var_str.to_string())]); + {{/isDeepObject}} {{/isArray}} } {{/required}} @@ -223,7 +244,7 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: {{/hasAuthMethods}} {{#isMultipart}} {{#hasFormParams}} - let mut local_var_form = reqwest::multipart::Form::new(); + let mut local_var_form = reqwest{{^supportAsync}}::blocking{{/supportAsync}}::multipart::Form::new(); {{#formParams}} {{#isFile}} {{^supportAsync}} diff --git a/support/openapi-template/reqwest/api_mod.mustache b/support/openapi-template/reqwest/api_mod.mustache index 628ec898a..347f33379 100644 --- a/support/openapi-template/reqwest/api_mod.mustache +++ b/support/openapi-template/reqwest/api_mod.mustache @@ -14,6 +14,9 @@ pub struct ResponseContent { #[derive(Debug)] pub enum Error { Reqwest(reqwest::Error), + {{#supportMiddleware}} + ReqwestMiddleware(reqwest_middleware::Error), + {{/supportMiddleware}} Serde(serde_json::Error), Io(std::io::Error), ResponseError(ResponseContent), @@ -26,12 +29,15 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (module, e) = match self { Error::Reqwest(e) => ("reqwest", e.to_string()), + {{#supportMiddleware}} + Error::ReqwestMiddleware(e) => ("reqwest-middleware", e.to_string()), + {{/supportMiddleware}} Error::Serde(e) => ("serde", e.to_string()), Error::Io(e) => ("IO", e.to_string()), Error::ResponseError(e) => ("response", format!("status code {}", e.status)), - {{#withAWSV4Signature}} + {{#withAWSV4Signature}} Error::AWSV4SignatureError(e) => ("aws v4 signature", e.to_string()), - {{/withAWSV4Signature}} + {{/withAWSV4Signature}} }; write!(f, "error in {}: {}", module, e) } @@ -41,12 +47,15 @@ impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { Some(match self { Error::Reqwest(e) => e, + {{#supportMiddleware}} + Error::ReqwestMiddleware(e) => e, + {{/supportMiddleware}} Error::Serde(e) => e, Error::Io(e) => e, Error::ResponseError(_) => return None, - {{#withAWSV4Signature}} - Error::AWSV4SignatureError(_) => return None, - {{/withAWSV4Signature}} + {{#withAWSV4Signature}} + Error::AWSV4SignatureError(_) => return None, + {{/withAWSV4Signature}} }) } } @@ -57,6 +66,14 @@ impl From for Error { } } +{{#supportMiddleware}} +impl From for Error { + fn from(e: reqwest_middleware::Error) -> Self { + Error::ReqwestMiddleware(e) + } +} + +{{/supportMiddleware}} impl From for Error { fn from(e: serde_json::Error) -> Self { Error::Serde(e) @@ -73,6 +90,35 @@ pub fn urlencode>(s: T) -> String { ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() } +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + }, + serde_json::Value::String(s) => params.push((format!("{}[{}]", prefix, key), s.clone())), + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + {{#apiInfo}} {{#apis}} pub mod {{{classFilename}}}; diff --git a/support/openapi-template/reqwest/configuration.mustache b/support/openapi-template/reqwest/configuration.mustache index cbc21644e..0de2884a8 100644 --- a/support/openapi-template/reqwest/configuration.mustache +++ b/support/openapi-template/reqwest/configuration.mustache @@ -1,7 +1,5 @@ {{>partial_header}} -use reqwest; - {{#withAWSV4Signature}} use std::time::SystemTime; use aws_sigv4::http_request::{sign, SigningSettings, SigningParams, SignableRequest}; @@ -13,7 +11,7 @@ use secrecy::{SecretString, ExposeSecret}; pub struct Configuration { pub base_path: String, pub user_agent: Option, - pub client: reqwest::Client, + pub client: {{#supportMiddleware}}reqwest_middleware::ClientWithMiddleware{{/supportMiddleware}}{{^supportMiddleware}}reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client{{/supportMiddleware}}, pub basic_auth: Option, pub oauth_access_token: Option, pub bearer_access_token: Option, @@ -82,7 +80,7 @@ impl Default for Configuration { Configuration { base_path: "{{{basePath}}}".to_owned(), user_agent: {{#httpUserAgent}}Some("{{{.}}}".to_owned()){{/httpUserAgent}}{{^httpUserAgent}}Some("OpenAPI-Generator/{{{version}}}/rust".to_owned()){{/httpUserAgent}}, - client: reqwest::Client::new(), + client: {{#supportMiddleware}}reqwest_middleware::ClientBuilder::new(reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client::new()).build(){{/supportMiddleware}}{{^supportMiddleware}}reqwest{{^supportAsync}}::blocking{{/supportAsync}}::Client::new(){{/supportMiddleware}}, basic_auth: None, oauth_access_token: None, bearer_access_token: None, diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 148b089e6..757878334 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -1,4 +1,10 @@ -import { quicktype, InputData, JSONSchemaInput, FetchingJSONSchemaStore } from "quicktype-core"; +import { + quicktype, + quicktypeMultiFile, + InputData, + JSONSchemaInput, + FetchingJSONSchemaStore, +} from "quicktype-core"; import fs from "fs"; import path from "path"; @@ -16,22 +22,16 @@ async function* walk(dir: string): AsyncIterable { async function main() { const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - - const filenames: string[] = []; - for await (const p of walk("./support/schemas")) { - filenames.push(p); - } - - filenames.sort(); - - for (const f of filenames) { - const buffer = fs.readFileSync(f); - const relative = path.relative(path.join(process.cwd(), "support/schemas"), f); - await schemaInput.addSource({ name: relative, schema: buffer.toString() }); - } - const inputData = new InputData(); inputData.addInput(schemaInput); + inputData.addSource( + "schema", + { + name: "SchemaTypes", + uris: ["support/schemas/schema_types/SchemaTypes.json#/definitions/"], + }, + () => new JSONSchemaInput(new FetchingJSONSchemaStore()), + ); const ts = await quicktype({ inputData, @@ -39,7 +39,7 @@ async function main() { rendererOptions: {}, }); - writeToFile("./languages/js_webassembly/bitwarden_client/schemas.ts", ts.lines); + writeToFile("./languages/js/sdk-client/src/schemas.ts", ts.lines); writeToFile("./crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts", ts.lines); const python = await quicktype({ @@ -50,7 +50,17 @@ async function main() { }, }); - writeToFile("./languages/python/BitwardenClient/schemas.py", python.lines); + writeToFile("./languages/python/bitwarden_sdk/schemas.py", python.lines); + + const ruby = await quicktype({ + inputData, + lang: "ruby", + rendererOptions: { + "ruby-version": "3.0", + }, + }); + + writeToFile("./languages/ruby/bitwarden_sdk_secrets/lib/schemas.rb", ruby.lines); const csharp = await quicktype({ inputData, @@ -63,6 +73,50 @@ async function main() { }); writeToFile("./languages/csharp/Bitwarden.Sdk/schemas.cs", csharp.lines); + + const cpp = await quicktype({ + inputData, + lang: "cpp", + rendererOptions: { + namespace: "Bitwarden::Sdk", + "include-location": "global-include", + }, + }); + + cpp.lines.forEach((line, idx) => { + // Replace DOMAIN for URI_DOMAIN, because DOMAIN is an already defined macro + cpp.lines[idx] = line.replace(/DOMAIN/g, "URI_DOMAIN"); + }); + + writeToFile("./languages/cpp/include/schemas.hpp", cpp.lines); + + const go = await quicktype({ + inputData, + lang: "go", + rendererOptions: { + package: "sdk", + "just-types-and-package": true, + }, + }); + + writeToFile("./languages/go/schema.go", go.lines); + + const java = await quicktypeMultiFile({ + inputData, + lang: "java", + rendererOptions: { + package: "com.bitwarden.sdk.schema", + "java-version": "8", + }, + }); + + const javaDir = "./languages/java/src/main/java/com/bitwarden/sdk/schema/"; + if (!fs.existsSync(javaDir)) { + fs.mkdirSync(javaDir); + } + java.forEach((file, path) => { + writeToFile(javaDir + path, file.lines); + }); } main();