diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6bb362e8c..e4fbb97b9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,3 +11,8 @@ # Secrets Manager team crates/bitwarden-sm @bitwarden/team-secrets-manager-dev crates/bws @bitwarden/team-secrets-manager-dev + +# BRE Automations +crates/bws/Cargo.toml +crates/bws/scripts/install.ps1 +crates/bws/scripts/install.sh diff --git a/.github/workflows/build-cli-docker.yml b/.github/workflows/build-cli-docker.yml index 8302529c8..eaf2299ff 100644 --- a/.github/workflows/build-cli-docker.yml +++ b/.github/workflows/build-cli-docker.yml @@ -5,8 +5,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" workflow_dispatch: pull_request: @@ -22,15 +20,9 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check Branch to Publish - env: - PUBLISH_BRANCHES: "master,rc,hotfix-rc" id: publish-branch-check run: | - REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} - - IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES - - if [[ "${publish_branches[*]}" =~ "${REF}" ]]; then + if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then echo "is_publish_branch=true" >> $GITHUB_ENV else echo "is_publish_branch=false" >> $GITHUB_ENV @@ -77,10 +69,8 @@ jobs: run: | REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} IMAGE_TAG=$(echo "${REF}" | sed "s#/#-#g") # slash safe branch name - if [[ "${IMAGE_TAG}" == "master" ]]; then + if [[ "${IMAGE_TAG}" == "main" ]]; then IMAGE_TAG=dev - elif [[ ("${IMAGE_TAG}" == "rc") || ("${IMAGE_TAG}" == "hotfix-rc") ]]; then - IMAGE_TAG=rc fi echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT @@ -89,9 +79,8 @@ jobs: id: tag-list env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} - IS_PUBLISH_BRANCH: ${{ env.is_publish_branch }} run: | - if [[ ("${IMAGE_TAG}" == "dev" || "${IMAGE_TAG}" == "rc") && "${IS_PUBLISH_BRANCH}" == "true" ]]; then + if [[ "${IMAGE_TAG}" == "dev" ]]; then echo "tags=$_AZ_REGISTRY/bws:${IMAGE_TAG},bitwarden/bws:${IMAGE_TAG}" >> $GITHUB_OUTPUT else echo "tags=$_AZ_REGISTRY/bws:${IMAGE_TAG}" >> $GITHUB_OUTPUT @@ -123,10 +112,7 @@ jobs: needs: build-docker steps: - name: Check if any job failed - if: | - github.ref == 'refs/heads/master' - || github.ref == 'refs/heads/rc' - || github.ref == 'refs/heads/hotfix-rc' + if: github.ref == 'refs/heads/main' env: BUILD_DOCKER_STATUS: ${{ needs.build-docker.result }} run: | diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index a54a669bb..355575716 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -6,8 +6,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" workflow_dispatch: defaults: @@ -132,8 +130,7 @@ jobs: build-macos: name: Building CLI for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} - needs: - - setup + needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} strategy: @@ -242,8 +239,7 @@ jobs: build-linux: name: Building CLI for - ${{ matrix.settings.os }} - ${{ matrix.settings.target }} runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} - needs: - - setup + needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} strategy: @@ -271,7 +267,8 @@ jobs: toolchain: stable targets: ${{ matrix.settings.target }} - - uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d5824358023be3a2802d # v2.2.0 + - name: Set up Zig + uses: goto-bus-stop/setup-zig@7ab2955eb728f5440978d5824358023be3a2802d # v2.2.0 with: version: 0.12.0 @@ -325,7 +322,7 @@ jobs: unzip bws-x86_64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip -d ./bws-x86_64-apple-darwin unzip bws-aarch64-apple-darwin-${{ env._PACKAGE_VERSION }}.zip -d ./bws-aarch64-apple-darwin - - name: lipo create universal package + - name: Create universal package with lipo run: | mkdir ./bws-macos-universal @@ -441,8 +438,7 @@ jobs: manpages: name: Generate manpages runs-on: ubuntu-22.04 - needs: - - setup + needs: setup steps: - name: Checkout repo uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 diff --git a/.github/workflows/build-rust-crates.yml b/.github/workflows/build-rust-crates.yml index 068bb9bbd..b7d0832fb 100644 --- a/.github/workflows/build-rust-crates.yml +++ b/.github/workflows/build-rust-crates.yml @@ -7,8 +7,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" env: CARGO_TERM_COLOR: always @@ -17,7 +15,7 @@ jobs: build: name: Building ${{matrix.package}} for - ${{ matrix.os }} - runs-on: ${{ matrix.settings.os || 'ubuntu-latest' }} + runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false @@ -42,7 +40,6 @@ jobs: uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable - targets: ${{ matrix.settings.target }} - name: Cache cargo registry uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 @@ -61,9 +58,8 @@ jobs: release-dry-run: name: Release dry-run runs-on: ubuntu-latest - if: ${{ github.ref == 'refs/head/main' || github.ref == 'refs/head/rc' || github.ref == 'refs/head/hotfix-rc' }} - needs: - - build + if: ${{ github.ref == 'refs/head/main' }} + needs: build steps: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 @@ -72,7 +68,6 @@ jobs: uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable with: toolchain: stable - targets: ${{ matrix.settings.target }} - name: Cache cargo registry uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 diff --git a/.github/workflows/build-rust-cross-platform.yml b/.github/workflows/build-rust-cross-platform.yml index a8c626d2c..7c567135b 100644 --- a/.github/workflows/build-rust-cross-platform.yml +++ b/.github/workflows/build-rust-cross-platform.yml @@ -60,14 +60,23 @@ jobs: - name: Add build architecture run: rustup target add ${{ matrix.settings.target }} + # Build Rust for musl - name: Build Rust for - ${{ matrix.settings.target }} if: ${{ contains(matrix.settings.target, 'musl') }} env: RUSTFLAGS: "-D warnings" run: cargo zigbuild -p bitwarden-c --target ${{ matrix.settings.target }} --release + # Build Rust for windows-gnu - name: Build Rust for - ${{ matrix.settings.target }} - if: ${{ !contains(matrix.settings.target, 'musl') }} + if: ${{ matrix.settings.target == 'x86_64-pc-windows-gnu' }} + env: + RUSTFLAGS: "-D warnings" + run: cargo build -p bitwarden-c --target ${{ matrix.settings.target }} --profile=release-windows + + # Build Rust for !musl && !windows-gnu + - name: Build Rust for - ${{ matrix.settings.target }} + if: ${{ !contains(matrix.settings.target, 'musl') && matrix.settings.target != 'x86_64-pc-windows-gnu' }} env: RUSTFLAGS: "-D warnings" MACOSX_DEPLOYMENT_TARGET: "10.14" # allows using new macos runner versions while still supporting older systems @@ -79,3 +88,4 @@ jobs: name: libbitwarden_c_files-${{ matrix.settings.target }} path: | target/${{ matrix.settings.target }}/release/*bitwarden_c* + target/${{ matrix.settings.target }}/release-windows/*bitwarden_c* diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml index c2c79d3ad..d57319dfc 100644 --- a/.github/workflows/build-wasm.yml +++ b/.github/workflows/build-wasm.yml @@ -1,5 +1,5 @@ --- -name: Build @bitwarden/sdk-wasm +name: Build WASM on: pull_request: @@ -16,8 +16,11 @@ defaults: working-directory: crates/bitwarden-wasm jobs: - build: - name: Building @bitwarden/sdk-wasm + generate_schemas: + uses: ./.github/workflows/generate_schemas.yml + + wasm: + name: Building @bitwarden/wasm runs-on: ubuntu-22.04 steps: @@ -27,7 +30,7 @@ jobs: - name: Setup Node uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: - node-version: 18 + node-version: 20 registry-url: "https://npm.pkg.github.com" cache: "npm" @@ -74,3 +77,50 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} working-directory: languages/js/wasm + + client: + name: Building @bitwarden/sdk-client + runs-on: ubuntu-22.04 + needs: generate_schemas + + steps: + - name: Checkout repo + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Node + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: 20 + registry-url: "https://npm.pkg.github.com" + cache: "npm" + + - name: Retrieve schemas + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: schemas.ts + path: ${{ github.workspace }}/languages/js/sdk-client/src/ + + - name: npm ci + run: npm ci + working-directory: languages/js/sdk-client + + - name: build + run: npm run build + working-directory: languages/js/sdk-client + + - 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-client@latest version) + npm version --no-git-tag-version prerelease + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + working-directory: languages/js/sdk-client + + - 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/sdk-client diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 9e2fc6d93..a2390953e 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -18,31 +18,23 @@ permissions: id-token: write jobs: - setup: - name: Setup + publish_ruby: + name: Publish Ruby runs-on: ubuntu-22.04 steps: - - name: Checkout repo + - name: Checkout Repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi - publish_ruby: - name: Publish Ruby - runs-on: ubuntu-22.04 - needs: setup - steps: - - name: Checkout Repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: Set up Ruby uses: ruby/setup-ruby@52753b7da854d5c07df37391a986c76ab4615999 # v1.191.0 with: @@ -54,7 +46,7 @@ jobs: 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 }} + branch: main artifacts: schemas.rb - name: Download x86_64-apple-darwin artifact @@ -63,7 +55,7 @@ jobs: 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 }} + branch: main artifacts: libbitwarden_c_files-x86_64-apple-darwin - name: Download aarch64-apple-darwin artifact @@ -71,7 +63,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-aarch64-apple-darwin path: temp/macos-arm64 @@ -80,7 +72,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-unknown-linux-gnu path: temp/linux-x64 @@ -89,7 +81,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ github.event.inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-pc-windows-msvc path: temp/windows-x64 @@ -128,6 +120,7 @@ jobs: working-directory: languages/ruby/bitwarden_sdk_secrets - name: Push gem to Rubygems + if: ${{ inputs.release_type != 'Dry Run' }} run: | mkdir -p $HOME/.gem touch $HOME/.gem/credentials diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index 3931e9e2a..bdb39d9b7 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -15,37 +15,24 @@ on: - Redeploy - Dry Run -defaults: - run: - shell: bash - jobs: - setup: - name: Setup + publish: + name: Publish runs-on: ubuntu-22.04 steps: - - name: Checkout repo + - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi - publish: - name: Publish - runs-on: ubuntu-latest - needs: - - setup - steps: - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: Login to Azure uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: @@ -70,7 +57,7 @@ jobs: run: cargo install cargo-release - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: @@ -81,14 +68,14 @@ jobs: task: release - name: Cargo release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} env: PUBLISH_GRACE_SLEEP: 10 CARGO_REGISTRY_TOKEN: ${{ steps.retrieve-secrets.outputs.cratesio-api-token }} run: cargo-release release publish --exclude bw --exclude bws --execute --no-confirm - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + if: ${{ inputs.release_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: "${{ secrets.GITHUB_TOKEN }}" @@ -96,7 +83,7 @@ jobs: deployment-id: ${{ steps.deployment.outputs.deployment_id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + if: ${{ inputs.release_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 009dc9359..80c09142e 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -14,10 +14,6 @@ on: - Release - Dry Run -defaults: - run: - shell: bash - env: _AZ_REGISTRY: bitwardenprod.azurecr.io @@ -32,11 +28,11 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-cli" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc-cli' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi @@ -48,7 +44,7 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7 id: deployment with: @@ -59,7 +55,6 @@ jobs: task: release - name: Download all Release artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-cli.yml @@ -67,15 +62,6 @@ jobs: workflow_conclusion: success branch: ${{ github.ref_name }} - - name: Dry Run - Download all artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build-cli.yml - path: packages - workflow_conclusion: success - branch: main - - name: Get checksum files uses: bitwarden/gh-actions/get-checksum@main with: @@ -83,7 +69,7 @@ jobs: file_path: "packages/bws-sha256-checksums-${{ steps.version.outputs.version }}.txt" - name: Create release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 env: PKG_VERSION: ${{ steps.version.outputs.version }} @@ -105,7 +91,7 @@ jobs: draft: true - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + if: ${{ inputs.release_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: "${{ secrets.GITHUB_TOKEN }}" @@ -113,7 +99,7 @@ jobs: deployment-id: ${{ steps.deployment.outputs.deployment_id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + if: ${{ inputs.release_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 with: token: "${{ secrets.GITHUB_TOKEN }}" @@ -123,8 +109,7 @@ jobs: publish: name: Publish bws to crates.io runs-on: ubuntu-22.04 - needs: - - setup + needs: setup steps: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 @@ -153,7 +138,7 @@ jobs: run: cargo install cargo-release - name: Cargo release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} env: PUBLISH_GRACE_SLEEP: 10 CARGO_REGISTRY_TOKEN: ${{ steps.retrieve-secrets.outputs.cratesio-api-token }} @@ -229,7 +214,7 @@ jobs: "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" - name: Log out of Docker and disable Docker Notary - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | docker logout echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml index 8d8b136b2..3d9ed6f00 100644 --- a/.github/workflows/rust-test.yml +++ b/.github/workflows/rust-test.yml @@ -5,8 +5,6 @@ on: push: branches: - "main" - - "rc" - - "hotfix-rc" pull_request: env: @@ -23,7 +21,7 @@ jobs: - run: exit 0 test: - name: ${{ matrix.os }} / ${{matrix.target || 'default' }} + name: ${{ matrix.os }} / default runs-on: ${{ matrix.os || 'ubuntu-22.04' }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 3536c96ce..8357d49bd 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,6 +1,6 @@ --- name: Version Bump -run-name: Version Bump - v${{ inputs.version_number }} +run-name: Version Bump - ${{ inputs.project }} - v${{ inputs.version_number }} on: workflow_dispatch: @@ -25,7 +25,7 @@ on: required: true cut_rc_branch: description: "Cut RC branch?" - default: true + default: false type: boolean jobs: @@ -198,7 +198,7 @@ jobs: env: GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} PR_BRANCH: ${{ steps.create-branch.outputs.name }} - TITLE: "Bump version to ${{ inputs.version_number }}" + TITLE: "Bump ${{ inputs.project }} version to ${{ inputs.version_number }}" run: | PR_URL=$(gh pr create --title "$TITLE" \ --base "main" \ diff --git a/Cargo.lock b/Cargo.lock index e2ad1f04f..068df1254 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,6 +518,7 @@ dependencies = [ "log", "p256", "passkey", + "passkey-client", "reqwest", "schemars", "serde", @@ -743,7 +744,7 @@ dependencies = [ [[package]] name = "bws" -version = "0.5.0" +version = "1.0.0" dependencies = [ "bat", "bitwarden", @@ -2633,8 +2634,8 @@ dependencies = [ [[package]] name = "passkey" -version = "0.3.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=ae08e2cb7dd3d44d915caed395c0cdc56b50fa27#ae08e2cb7dd3d44d915caed395c0cdc56b50fa27" +version = "0.2.0" +source = "git+https://github.com/bitwarden/passkey-rs?rev=29bb052eb15a42e369728ded3cfb2aa7c91213df#29bb052eb15a42e369728ded3cfb2aa7c91213df" dependencies = [ "passkey-authenticator", "passkey-client", @@ -2644,8 +2645,8 @@ dependencies = [ [[package]] name = "passkey-authenticator" -version = "0.3.0" -source = "git+https://github.com/bitwarden/passkey-rs?rev=ae08e2cb7dd3d44d915caed395c0cdc56b50fa27#ae08e2cb7dd3d44d915caed395c0cdc56b50fa27" +version = "0.2.0" +source = "git+https://github.com/bitwarden/passkey-rs?rev=29bb052eb15a42e369728ded3cfb2aa7c91213df#29bb052eb15a42e369728ded3cfb2aa7c91213df" dependencies = [ "async-trait", "coset", @@ -2657,12 +2658,13 @@ dependencies = [ [[package]] name = "passkey-client" -version = "0.3.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=ae08e2cb7dd3d44d915caed395c0cdc56b50fa27#ae08e2cb7dd3d44d915caed395c0cdc56b50fa27" +version = "0.2.0" +source = "git+https://github.com/bitwarden/passkey-rs?rev=29bb052eb15a42e369728ded3cfb2aa7c91213df#29bb052eb15a42e369728ded3cfb2aa7c91213df" dependencies = [ "ciborium", "coset", "idna", + "nom", "passkey-authenticator", "passkey-types", "public-suffix", @@ -2674,12 +2676,12 @@ dependencies = [ [[package]] name = "passkey-transports" version = "0.1.0" -source = "git+https://github.com/bitwarden/passkey-rs?rev=ae08e2cb7dd3d44d915caed395c0cdc56b50fa27#ae08e2cb7dd3d44d915caed395c0cdc56b50fa27" +source = "git+https://github.com/bitwarden/passkey-rs?rev=29bb052eb15a42e369728ded3cfb2aa7c91213df#29bb052eb15a42e369728ded3cfb2aa7c91213df" [[package]] name = "passkey-types" version = "0.2.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=ae08e2cb7dd3d44d915caed395c0cdc56b50fa27#ae08e2cb7dd3d44d915caed395c0cdc56b50fa27" +source = "git+https://github.com/bitwarden/passkey-rs?rev=29bb052eb15a42e369728ded3cfb2aa7c91213df#29bb052eb15a42e369728ded3cfb2aa7c91213df" dependencies = [ "bitflags 2.6.0", "ciborium", @@ -2916,7 +2918,7 @@ dependencies = [ [[package]] name = "public-suffix" version = "0.1.1" -source = "git+https://github.com/bitwarden/passkey-rs?rev=ae08e2cb7dd3d44d915caed395c0cdc56b50fa27#ae08e2cb7dd3d44d915caed395c0cdc56b50fa27" +source = "git+https://github.com/bitwarden/passkey-rs?rev=29bb052eb15a42e369728ded3cfb2aa7c91213df#29bb052eb15a42e369728ded3cfb2aa7c91213df" [[package]] name = "pyo3" diff --git a/Cargo.toml b/Cargo.toml index 7d452869c..8c978819b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,13 @@ opt-level = 1 [profile.release] lto = "thin" codegen-units = 1 + +# Turn off LTO on release mode for windows +# This is a workaround until this is fixed: https://github.com/rustls/rustls-platform-verifier/issues/141 +[profile.release-windows] +inherits = "release" +lto = "off" + # Stripping the binary reduces the size by ~30%, but the stacktraces won't be usable anymore. # This is fine as long as we don't have any unhandled panics, but let's keep it disabled for now # strip = true diff --git a/crates/bitwarden-fido/Cargo.toml b/crates/bitwarden-fido/Cargo.toml index e85e56a19..6b04bd796 100644 --- a/crates/bitwarden-fido/Cargo.toml +++ b/crates/bitwarden-fido/Cargo.toml @@ -30,7 +30,10 @@ coset = { version = "0.3.7" } itertools = "0.13.0" log = ">=0.4.18, <0.5" p256 = { version = ">=0.13.2, <0.14" } -passkey = { git = "https://github.com/bitwarden/passkey-rs", rev = "ae08e2cb7dd3d44d915caed395c0cdc56b50fa27" } +passkey = { git = "https://github.com/bitwarden/passkey-rs", rev = "ff757604cd7b4e8f321ed1616fef7e40e21ac5df" } +passkey-client = { git = "https://github.com/bitwarden/passkey-rs", rev = "ff757604cd7b4e8f321ed1616fef7e40e21ac5df", features = [ + "android-asset-validation", +] } reqwest = { version = ">=0.12.5, <0.13", default-features = false } schemars = { version = "0.8.21", features = ["uuid1", "chrono"] } serde = { version = ">=1.0, <2.0", features = ["derive"] } diff --git a/crates/bitwarden-fido/src/client.rs b/crates/bitwarden-fido/src/client.rs index a72dae6f5..ac4330be7 100644 --- a/crates/bitwarden-fido/src/client.rs +++ b/crates/bitwarden-fido/src/client.rs @@ -1,5 +1,4 @@ use passkey::client::WebauthnError; -use reqwest::Url; use thiserror::Error; use super::{ @@ -7,15 +6,12 @@ use super::{ get_string_name_from_enum, types::{ AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, ClientData, - ClientExtensionResults, CredPropsResult, + ClientExtensionResults, CredPropsResult, Origin, }, Fido2Authenticator, PublicKeyCredentialAuthenticatorAssertionResponse, PublicKeyCredentialAuthenticatorAttestationResponse, }; - -#[derive(Debug, Error)] -#[error("Invalid origin: {0}")] -pub struct InvalidOriginError(String); +use crate::types::InvalidOriginError; #[derive(Debug, Error)] pub enum Fido2ClientError { @@ -43,12 +39,11 @@ pub struct Fido2Client<'a> { impl<'a> Fido2Client<'a> { pub async fn register( &mut self, - origin: String, + origin: Origin, request: String, client_data: ClientData, ) -> Result { - let origin = Url::parse(&origin).map_err(|e| InvalidOriginError(format!("{}", e)))?; - + let origin: passkey::client::Origin = origin.try_into()?; let request: passkey::types::webauthn::CredentialCreationOptions = serde_json::from_str(&request)?; @@ -67,7 +62,7 @@ impl<'a> Fido2Client<'a> { let rp_id = request.public_key.rp.id.clone(); let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(true)); - let result = client.register(&origin, request, client_data).await?; + let result = client.register(origin, request, client_data).await?; Ok(PublicKeyCredentialAuthenticatorAttestationResponse { id: result.id, @@ -98,12 +93,11 @@ impl<'a> Fido2Client<'a> { pub async fn authenticate( &mut self, - origin: String, + origin: Origin, request: String, client_data: ClientData, ) -> Result { - let origin = Url::parse(&origin).map_err(|e| InvalidOriginError(format!("{}", e)))?; - + let origin: passkey::client::Origin = origin.try_into()?; let request: passkey::types::webauthn::CredentialRequestOptions = serde_json::from_str(&request)?; @@ -116,7 +110,7 @@ impl<'a> Fido2Client<'a> { .replace(uv); let mut client = passkey::client::Client::new(self.authenticator.get_authenticator(false)); - let result = client.authenticate(&origin, request, client_data).await?; + let result = client.authenticate(origin, request, client_data).await?; Ok(PublicKeyCredentialAuthenticatorAssertionResponse { id: result.id, diff --git a/crates/bitwarden-fido/src/lib.rs b/crates/bitwarden-fido/src/lib.rs index 991828eb3..be1dfdb53 100644 --- a/crates/bitwarden-fido/src/lib.rs +++ b/crates/bitwarden-fido/src/lib.rs @@ -32,10 +32,10 @@ pub use traits::{ pub use types::{ AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, ClientData, Fido2CredentialAutofillView, Fido2CredentialAutofillViewError, GetAssertionRequest, - GetAssertionResult, MakeCredentialRequest, MakeCredentialResult, Options, + GetAssertionResult, MakeCredentialRequest, MakeCredentialResult, Options, Origin, PublicKeyCredentialAuthenticatorAssertionResponse, PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity, - PublicKeyCredentialUserEntity, + PublicKeyCredentialUserEntity, UnverifiedAssetLink, }; use self::crypto::{cose_key_to_pkcs8, pkcs8_to_cose_key}; diff --git a/crates/bitwarden-fido/src/types.rs b/crates/bitwarden-fido/src/types.rs index 8bc8ae42c..409db7a98 100644 --- a/crates/bitwarden-fido/src/types.rs +++ b/crates/bitwarden-fido/src/types.rs @@ -1,7 +1,10 @@ +use std::borrow::Cow; + use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use bitwarden_crypto::KeyContainer; use bitwarden_vault::{CipherError, CipherView}; use passkey::types::webauthn::UserVerificationRequirement; +use reqwest::Url; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -359,6 +362,67 @@ pub struct AuthenticatorAssertionResponse { pub user_handle: Vec, } +#[derive(Debug, Error)] +#[error("Invalid origin: {0}")] +pub struct InvalidOriginError(String); + +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +/// An Unverified asset link. +pub struct UnverifiedAssetLink { + /// Application package name. + package_name: String, + /// Fingerprint to compare. + sha256_cert_fingerprint: String, + /// Host to lookup the well known asset link. + host: String, + /// When sourced from the application statement list or parsed from host for passkeys. + /// Will be generated from `host` if not provided. + asset_link_url: Option, +} + +#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))] +/// The origin of a WebAuthn request. +pub enum Origin { + /// A Url, meant for a request in the web browser. + Web(String), + /// An android digital asset fingerprint. + /// Meant for a request coming from an android application. + Android(UnverifiedAssetLink), +} + +impl<'a> TryFrom for passkey::client::Origin<'a> { + type Error = InvalidOriginError; + + fn try_from(value: Origin) -> Result { + Ok(match value { + Origin::Web(url) => { + let url = Url::parse(&url).map_err(|e| InvalidOriginError(format!("{}", e)))?; + passkey::client::Origin::Web(Cow::Owned(url)) + } + Origin::Android(link) => passkey::client::Origin::Android(link.try_into()?), + }) + } +} + +impl<'a> TryFrom for passkey::client::UnverifiedAssetLink<'a> { + type Error = InvalidOriginError; + + fn try_from(value: UnverifiedAssetLink) -> Result { + let asset_link_url = match value.asset_link_url { + Some(url) => Some(Url::parse(&url).map_err(|e| InvalidOriginError(format!("{}", e)))?), + None => None, + }; + + passkey::client::UnverifiedAssetLink::new( + Cow::from(value.package_name), + value.sha256_cert_fingerprint.as_str(), + Cow::from(value.host), + asset_link_url, + ) + .map_err(|e| InvalidOriginError(format!("{:?}", e))) + } +} + #[cfg(test)] mod tests { use serde::{Deserialize, Serialize}; diff --git a/crates/bitwarden-napi/README.md b/crates/bitwarden-napi/README.md index e9c3d0a71..6fce8d069 100644 --- a/crates/bitwarden-napi/README.md +++ b/crates/bitwarden-napi/README.md @@ -17,11 +17,12 @@ const settings: ClientSettings = { }; const accessToken = "-- REDACTED --"; +const stateFile = "some/path/to/state/file"; const client = new BitwardenClient(settings, LogLevel.Info); // Authenticating using a machine account access token -await client.accessTokenLogin(accessToken); +await client.auth().loginAccessToken(accessToken, stateFile); // List secrets const secrets = await client.secrets().list(); diff --git a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts index 52a53ef4f..3a3765a12 100644 --- a/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts +++ b/crates/bitwarden-napi/src-ts/bitwarden_client/index.ts @@ -37,19 +37,6 @@ export class BitwardenClient { this.client = new rust.BitwardenClient(settingsJson, loggingLevel ?? LogLevel.Info); } - async accessTokenLogin(accessToken: string, stateFile?: string): Promise { - const response = await this.client.runCommand( - Convert.commandToJson({ - accessTokenLogin: { - accessToken, - stateFile, - }, - }), - ); - - handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); - } - secrets(): SecretsClient { return new SecretsClient(this.client); } @@ -57,6 +44,10 @@ export class BitwardenClient { projects(): ProjectsClient { return new ProjectsClient(this.client); } + + auth(): AuthClient { + return new AuthClient(this.client); + } } export class SecretsClient { @@ -91,11 +82,11 @@ export class SecretsClient { } async create( + organizationId: string, key: string, value: string, note: string, projectIds: string[], - organizationId: string, ): Promise { const response = await this.client.runCommand( Convert.commandToJson({ @@ -121,12 +112,12 @@ export class SecretsClient { } async update( + organizationId: string, id: string, key: string, value: string, note: string, projectIds: string[], - organizationId: string, ): Promise { const response = await this.client.runCommand( Convert.commandToJson({ @@ -183,7 +174,7 @@ export class ProjectsClient { return handleResponse(Convert.toResponseForProjectResponse(response)); } - async create(name: string, organizationId: string): Promise { + async create(organizationId: string, name: string): Promise { const response = await this.client.runCommand( Convert.commandToJson({ projects: { @@ -207,7 +198,7 @@ export class ProjectsClient { return handleResponse(Convert.toResponseForProjectsResponse(response)); } - async update(id: string, name: string, organizationId: string): Promise { + async update(organizationId: string, id: string, name: string): Promise { const response = await this.client.runCommand( Convert.commandToJson({ projects: { @@ -231,3 +222,24 @@ export class ProjectsClient { return handleResponse(Convert.toResponseForProjectsDeleteResponse(response)); } } + +export class AuthClient { + client: rust.BitwardenClient; + + constructor(client: rust.BitwardenClient) { + this.client = client; + } + + async loginAccessToken(accessToken: string, stateFile?: string): Promise { + const response = await this.client.runCommand( + Convert.commandToJson({ + loginAccessToken: { + accessToken, + stateFile, + }, + }), + ); + + handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); + } +} diff --git a/crates/bitwarden-uniffi/src/platform/fido2.rs b/crates/bitwarden-uniffi/src/platform/fido2.rs index 3018d7beb..f483ff346 100644 --- a/crates/bitwarden-uniffi/src/platform/fido2.rs +++ b/crates/bitwarden-uniffi/src/platform/fido2.rs @@ -11,7 +11,7 @@ use bitwarden::{ }, vault::{Cipher, CipherView, Fido2CredentialNewView}, }; -use bitwarden_fido::Fido2CredentialAutofillView; +use bitwarden_fido::{Fido2CredentialAutofillView, Origin}; use crate::{error::Result, Client}; @@ -135,7 +135,7 @@ pub struct ClientFido2Client(pub(crate) ClientFido2Authenticator); impl ClientFido2Client { pub async fn register( &self, - origin: String, + origin: Origin, request: String, client_data: ClientData, ) -> Result { @@ -153,7 +153,7 @@ impl ClientFido2Client { pub async fn authenticate( &self, - origin: String, + origin: Origin, request: String, client_data: ClientData, ) -> Result { diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index 1b13c68a1..c6ef58396 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bws" -version = "0.5.0" +version = "1.0.0" description = """ Bitwarden Secrets Manager CLI """ diff --git a/crates/bws/scripts/install.ps1 b/crates/bws/scripts/install.ps1 index f39846c20..daa5cf9d1 100755 --- a/crates/bws/scripts/install.ps1 +++ b/crates/bws/scripts/install.ps1 @@ -4,7 +4,7 @@ param ( $ErrorActionPreference = "Stop" -$defaultBwsVersion = "0.5.0" +$defaultBwsVersion = "1.0.0" $bwsVersion = if ($env:bwsVersion) { $env:bwsVersion } else { $defaultBwsVersion } $installDir = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData) | Join-Path -ChildPath "Programs" | Join-Path -ChildPath "Bitwarden" diff --git a/crates/bws/scripts/install.sh b/crates/bws/scripts/install.sh index 126ae9e22..6cd7fe01f 100755 --- a/crates/bws/scripts/install.sh +++ b/crates/bws/scripts/install.sh @@ -4,7 +4,7 @@ # An installer for the bws command line utility. # ################################################## -DEFAULT_BWS_VERSION="0.5.0" +DEFAULT_BWS_VERSION="1.0.0" BWS_VERSION="${BWS_VERSION:-$DEFAULT_BWS_VERSION}" main() { diff --git a/languages/go/internal/cinterface/bitwarden_library.go b/languages/go/internal/cinterface/bitwarden_library.go index 3a63be61b..69d752716 100644 --- a/languages/go/internal/cinterface/bitwarden_library.go +++ b/languages/go/internal/cinterface/bitwarden_library.go @@ -11,7 +11,7 @@ import ( #cgo linux,arm64 LDFLAGS: -L ./lib/linux-arm64 #cgo darwin,amd64 LDFLAGS: -L ./lib/darwin-x64 -framework Security -framework SystemConfiguration #cgo darwin,arm64 LDFLAGS: -L ./lib/darwin-arm64 -framework Security -framework SystemConfiguration -#cgo windows,amd64 LDFLAGS: -L ./lib/windows-x64 -lbitwarden_c -ladvapi32 -lbcrypt -lcrypt32 -lcryptnet -lkernel32 -lncrypt -lntdll -luserenv -lws2_32 -lmsvcrt +#cgo windows,amd64 LDFLAGS: -L ./lib/windows-x64 -lbitwarden_c -ladvapi32 -lbcrypt -lcrypt32 -lcryptnet -lkernel32 -lncrypt -lntdll -luserenv -lws2_32 -lmsvcrt -loleaut32 -lruntimeobject #include typedef void* ClientPtr; extern char* run_command(const char *command, ClientPtr client); diff --git a/languages/js/INSTALL.md b/languages/js/INSTALL.md new file mode 100644 index 000000000..8497e873f --- /dev/null +++ b/languages/js/INSTALL.md @@ -0,0 +1,14 @@ +# WASM SDK Instructions + +## Steps + +1. Build the wasm binding + - Follow the instructions [here](../../crates/bitwarden-wasm/README.md) + - `cd sdk/crates/bitwarden-wasm` + - `./build.sh` +2. Change directory to the sdk-client and build it + - `cd sdk/languages/js/sdk-client/` + - `npm run build` +3. Change directory to the example directory and build it + - `cd sdk/languages/js/example` + - `npm run start` diff --git a/languages/js/example/index.js b/languages/js/example/index.js index 8da3fc582..3907636cf 100644 --- a/languages/js/example/index.js +++ b/languages/js/example/index.js @@ -11,22 +11,50 @@ async function main() { new BitwardenClientWasm(JSON.stringify(settings), LogLevel.Debug), ); + await client.auth().loginAccessToken(process.env.ACCESS_TOKEN); const organization_id = process.env.ORGANIZATION_ID; - await client.accessTokenLogin(process.env.ACCESS_TOKEN); - const project = await client.projects().create("test", organization_id); + // Project functions + + const project = await client.projects().create(organization_id, "project-name"); + console.log(project); + + const project_get = await client.projects().get(project.id); + console.log(project_get); + const projects = await client.projects().list(organization_id); console.log(projects.data); + const updated_project = await client.projects().update(organization_id, project.id, "project-name-updated"); + console.log(updated_project); + + // Secret functions + const secret = await client .secrets() - .create("test-secret", "My secret!", "This is my secret", [project.id], organization_id); + .create(organization_id, "secret-key", "secret-value", "secret-note", [project.id]); + console.log(secret); + + const secret_get = await client.secrets().get(secret.id); + console.log(secret_get); + const secrets = await client.secrets().list(organization_id); console.log(secrets.data); - console.log(project, secret); + const secrets_by_ids = await client.secrets().getByIds([secret.id]); + console.log(secrets_by_ids.data); + + const updated_secret = await client.secrets().update(organization_id, secret.id, "secret-key-updated", "secret-value-updated", "secret-note-updated", [project.id]); + console.log(updated_secret); + + const now = new Date(); + const secret_sync = await client.secrets().sync(organization_id, now); + console.log(secret_sync.hasChanges); + + // Delete functions - await client.projects().delete([project.id]); await client.secrets().delete([secret.id]); + await client.projects().delete([project.id]); } + main(); diff --git a/languages/js/sdk-client/package.json b/languages/js/sdk-client/package.json index 17269acc6..734384aef 100644 --- a/languages/js/sdk-client/package.json +++ b/languages/js/sdk-client/package.json @@ -10,6 +10,7 @@ "types": "dist/src/lib.d.ts", "scripts": { "build": "npm run clean && tsc", + "build:watch": "npm run clean && tsc --watch", "clean": "rimraf dist" }, "devDependencies": { diff --git a/languages/js/sdk-client/src/client.ts b/languages/js/sdk-client/src/client.ts index 0f06889c1..bd8268709 100644 --- a/languages/js/sdk-client/src/client.ts +++ b/languages/js/sdk-client/src/client.ts @@ -1,11 +1,14 @@ import { Convert, + PasswordGeneratorRequest, ProjectResponse, ProjectsDeleteResponse, ProjectsResponse, SecretIdentifiersResponse, SecretResponse, SecretsDeleteResponse, + SecretsSyncResponse, + SecretsResponse, } from "./schemas"; interface BitwardenSDKClient { @@ -26,16 +29,8 @@ export class BitwardenClient { this.client = client; } - async accessTokenLogin(accessToken: string): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - accessTokenLogin: { - accessToken, - }, - }), - ); - - handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); + auth(): AuthClient { + return new AuthClient(this.client); } secrets(): SecretsClient { @@ -45,6 +40,10 @@ export class BitwardenClient { projects(): ProjectsClient { return new ProjectsClient(this.client); } + + generators(): GeneratorsClient { + return new GeneratorsClient(this.client); + } } export class SecretsClient { @@ -67,11 +66,11 @@ export class SecretsClient { } async create( + organizationId: string, key: string, value: string, note: string, projectIds: string[], - organizationId: string, ): Promise { const response = await this.client.run_command( Convert.commandToJson({ @@ -97,12 +96,12 @@ export class SecretsClient { } async update( + organizationId: string, id: string, key: string, value: string, note: string, projectIds: string[], - organizationId: string, ): Promise { const response = await this.client.run_command( Convert.commandToJson({ @@ -126,6 +125,30 @@ export class SecretsClient { return handleResponse(Convert.toResponseForSecretsDeleteResponse(response)); } + + async sync(organizationId: string, lastSyncedDate?: Date): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + secrets: { + sync: { organizationId, lastSyncedDate }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretsSyncResponse(response)); + } + + async getByIds(ids: string[]): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + secrets: { + getByIds: { ids }, + }, + }), + ); + + return handleResponse(Convert.toResponseForSecretsResponse(response)); + } } export class ProjectsClient { @@ -147,7 +170,7 @@ export class ProjectsClient { return handleResponse(Convert.toResponseForProjectResponse(response)); } - async create(name: string, organizationId: string): Promise { + async create(organizationId: string, name: string): Promise { const response = await this.client.run_command( Convert.commandToJson({ projects: { @@ -171,7 +194,7 @@ export class ProjectsClient { return handleResponse(Convert.toResponseForProjectsResponse(response)); } - async update(id: string, name: string, organizationId: string): Promise { + async update(organizationId: string, id: string, name: string): Promise { const response = await this.client.run_command( Convert.commandToJson({ projects: { @@ -195,3 +218,43 @@ export class ProjectsClient { return handleResponse(Convert.toResponseForProjectsDeleteResponse(response)); } } + +export class GeneratorsClient { + client: BitwardenSDKClient; + + constructor(client: BitwardenSDKClient) { + this.client = client; + } + + async password(req: PasswordGeneratorRequest): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + generators: { + generatePassword: req, + }, + }), + ); + + return handleResponse(Convert.toResponseForString(response)); + } +} + +export class AuthClient { + client: BitwardenSDKClient; + + constructor(client: BitwardenSDKClient) { + this.client = client; + } + + async loginAccessToken(accessToken: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + loginAccessToken: { + accessToken, + }, + }), + ); + + handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); + } +} diff --git a/languages/js/wasm/index.js b/languages/js/wasm/index.js index c2b512005..cfb528a5c 100644 --- a/languages/js/wasm/index.js +++ b/languages/js/wasm/index.js @@ -23,3 +23,6 @@ if (supported) { import { __wbg_set_wasm } from "./bitwarden_wasm_bg.js"; __wbg_set_wasm(wasm); export * from "./bitwarden_wasm_bg.js"; + +// Expose if wasm is supported or not +export const supportsWasm = supported; diff --git a/languages/php/.gitignore b/languages/php/.gitignore index b2a69e9a0..5d6ed424e 100644 --- a/languages/php/.gitignore +++ b/languages/php/.gitignore @@ -1,2 +1,4 @@ .DS_Store vendor +src/lib/ +src/Schemas/ diff --git a/languages/php/INSTALL.md b/languages/php/INSTALL.md new file mode 100644 index 000000000..299053389 --- /dev/null +++ b/languages/php/INSTALL.md @@ -0,0 +1,56 @@ +# PHP Installation + +## Introduction + +Composer is used to build the PHP Bitwarden client library. + +## Prerequisites + +- PHP >= 8.0 +- FFI extension enabled in PHP configuration +- Composer +- Bitwarden SDK native library. + - Expected in one of below locations, depending on the OS and architecture. + The `src` is relative path to the [src](./src) directory. + - Windows x86_64: `src\lib\windows-x64\bitwarden_c.dll` + - Linux x86_64: `src/lib/linux-x64/libbitwarden_c.so` + - macOS x86_64: `src/lib/macos-x64/libbitwarden_c.dylib` + - macOS aarch64: `src/lib/macos-arm64/libbitwarden_c.dylib` + - If you prefer to build the SDK yourself, see the [SDK README.md](../../README.md) for instructions. + +## Build Commands + +```shell +composer install +``` + +## Example + +### macOS + +#### Install Prerequisites + +Use brew Composer and PHP + +```shell +brew install php +brew install composer +``` + +#### Build Commands + +```shell +composer install +``` + +## Example SDK Usage Project + +```shell +export ACCESS_TOKEN="" +export STATE_FILE="" +export ORGANIZATION_ID="" +export API_URL="https://api.bitwarden.com" +export IDENTITY_URL="https://identity.bitwarden.com" + +php example.php +``` diff --git a/languages/php/README.md b/languages/php/README.md index 9e4a9385d..61991bd0e 100644 --- a/languages/php/README.md +++ b/languages/php/README.md @@ -1,100 +1,121 @@ # Bitwarden Secrets Manager SDK wrapper for PHP PHP bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some functionality. -Supported are CRUD operations on project and secret entities. ## Installation -Requirements: -- PHP >= 8.0 -- Composer -- Bitwarden C libraries which you can generate using BitwardenSDK and following instructions in its readme (requires Rust). https://github.com/bitwarden/sdk -If you are not using the standalone version of this library, file will be placed in `target/debug` folder if you are using from BitwardenSDK repository. -- Access token for the Bitwarden account - +See the [installation instructions](./INSTALL.md) ## Usage -To interact with the client first you need to obtain the access token from Bitwarden. -You can then initialize BitwardenSettings passing $api_url and $identity_url if needed. These parameteres are -optional and if they are not defined, BitwardenSettings instance will try to get these values from ENV, and -if they are not defined there as well, it will use defaults: `https://api.bitwarden.com` as api_url and -`https://identity.bitwarden.com` as identity_url. You can also pass device type as argument but that is entirely -optional. +### Create access token -Passing BitwardenSettings instance to BitwardenClient will initialize it. Before using the client you must -be authorized by calling the access_token_login method passing your Bitwarden access token to it. +To interact with the client first you need to obtain the access token from Bitwarden. +Review the help documentation on [Access Tokens]. +### Create new Bitwarden client ```php -$access_token = ''; -$api_url = ""; -$identity_url = ""; +require_once 'vendor/autoload.php'; + +$access_token = ""; +$state_file = ""; +$organization_id = ""; +$api_url = "https://api.bitwarden.com"; +$identity_url = "https://identity.bitwarden.com"; + $bitwarden_settings = new \Bitwarden\Sdk\BitwardenSettings($api_url, $identity_url); $bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($bitwarden_settings); -$bitwarden_client->access_token_login($access_token); +$bitwarden_client->auth->login_access_token($access_token, $state_file); ``` -After successful authorization you can interact with client to manage your projects and secrets. -```php -$organization_id = ""; +Initialize `BitwardenSettings` by passing `$api_url` and `$identity_url` or set to null to use the defaults. +The default for `api_url` is `https://api.bitwarden.com` and for `identity_url` is `https://identity.bitwarden.com`. -$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($bitwarden_settings); -$res = $bitwarden_client->access_token_login($access_token); +### Create new project -// create project -$name = "PHP project" -$res = $bitwarden_client->projects->create($name, $organization_id); +```php +$name = "PHP project"; +$res = $bitwarden_client->projects->create($organization_id, $name); $project_id = $res->id; +``` -// get project +### Get project + +```php $res = $bitwarden_client->projects->get($project_id); +``` + +### List all projects -// list projects +```php $res = $bitwarden_client->projects->list($organization_id); +``` -// update project -$name = "Updated PHP project" -$res = $bitwarden_client->projects->put($project_id, $name, $organization_id); +### Update project -// get secret -$res = $bitwarden_client->secrets->get($secret_id); +```php +$name = "Updated PHP project"; +$res = $bitwarden_client->projects->update($organization_id, $project_id, $name); +``` -// list secrets -$res = $bitwarden_client->secrets->list($organization_id); +### Delete project -// delete project +```php $res = $bitwarden_client->projects->delete([$project_id]); +``` +### Create new secret + +```php +$key = "Secret key"; +$note = "Secret note"; +$value = "Secret value"; +$res = $bitwarden_client->secrets->create($organization_id, $key, $value, $note, [$project_id]); +$secret_id = $res->id; ``` -Similarly, you interact with secrets: +### Get secret + ```php -$organization_id = ""; +$res = $bitwarden_client->secrets->get($secret_id); +``` -// create secret -$key = "AWS secret key"; -$note = "Private account"; -$secret = "76asaj,Is_)" -$res = $bitwarden_client->secrets->create($key, $note, $organization_id, [$project_id], $secret); -$secret_id = $res->id; +### Get multiple secrets -// get secret -$res = $bitwarden_sdk->secrets->get($secret_id); +```php +$res = $bitwarden_client->secrets->get_by_ids([$secret_id]); +``` -// list secrets +### List all secrets + +```php $res = $bitwarden_client->secrets->list($organization_id); +``` + +### Update secret + +```php +$key = "Updated key"; +$note = "Updated note"; +$value = "Updated value"; +$res = $bitwarden_client->secrets->update($organization_id, $secret_id, $key, $value, $note, [$project_id]); +``` -// update secret -$note = "Updated account"; -$key = "AWS private updated" -$secret = "7uYTE,:Aer" -$res = $bitwarden_client->secrets->update($secret_id, $key, $note, $organization_id, [$project_id], $secret); +### Sync secrets -// delete secret -$res = $bitwarden_sdk->secrets->delete([$secret_id]); +```php +$last_synced_date = "2024-09-01T00:00:00Z"; +$res = $bitwarden_client->secrets->sync($organization_id, $last_synced_date); +``` + +### Delete secret + +```php +$res = $bitwarden_client->secrets->delete([$secret_id]); ``` +[Access Tokens]: https://bitwarden.com/help/access-tokens/ [Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/ diff --git a/languages/php/composer.json b/languages/php/composer.json index 85447e72a..fce61f890 100644 --- a/languages/php/composer.json +++ b/languages/php/composer.json @@ -7,13 +7,13 @@ "version": "0.1.0", "require": { "php": "^8.0", - "swaggest/json-schema": "^0.12.42", "ext-ffi": "*" }, "autoload": { "psr-4": { "Bitwarden\\Sdk\\": "src/" - } + }, + "files": ["src/Schemas/Schemas.php"] }, "authors": [ { diff --git a/languages/php/composer.lock b/languages/php/composer.lock index fc6b42c4f..187511304 100644 --- a/languages/php/composer.lock +++ b/languages/php/composer.lock @@ -4,234 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7081b1bfe099982a63ad06d5ab9fa66d", - "packages": [ - { - "name": "phplang/scope-exit", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/phplang/scope-exit.git", - "reference": "239b73abe89f9414aa85a7ca075ec9445629192b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phplang/scope-exit/zipball/239b73abe89f9414aa85a7ca075ec9445629192b", - "reference": "239b73abe89f9414aa85a7ca075ec9445629192b", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpLang\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD" - ], - "authors": [ - { - "name": "Sara Golemon", - "email": "pollita@php.net", - "homepage": "https://twitter.com/SaraMG", - "role": "Developer" - } - ], - "description": "Emulation of SCOPE_EXIT construct from C++", - "homepage": "https://github.com/phplang/scope-exit", - "keywords": [ - "cleanup", - "exit", - "scope" - ], - "support": { - "issues": "https://github.com/phplang/scope-exit/issues", - "source": "https://github.com/phplang/scope-exit/tree/master" - }, - "time": "2016-09-17T00:15:18+00:00" - }, - { - "name": "swaggest/json-diff", - "version": "v3.10.4", - "source": { - "type": "git", - "url": "https://github.com/swaggest/json-diff.git", - "reference": "f4e511708060ff7511a3743fab4aa484a062bcfb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swaggest/json-diff/zipball/f4e511708060ff7511a3743fab4aa484a062bcfb", - "reference": "f4e511708060ff7511a3743fab4aa484a062bcfb", - "shasum": "" - }, - "require": { - "ext-json": "*" - }, - "require-dev": { - "phperf/phpunit": "4.8.37" - }, - "type": "library", - "autoload": { - "psr-4": { - "Swaggest\\JsonDiff\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Viacheslav Poturaev", - "email": "vearutop@gmail.com" - } - ], - "description": "JSON diff/rearrange/patch/pointer library for PHP", - "support": { - "issues": "https://github.com/swaggest/json-diff/issues", - "source": "https://github.com/swaggest/json-diff/tree/v3.10.4" - }, - "time": "2022-11-09T13:21:05+00:00" - }, - { - "name": "swaggest/json-schema", - "version": "v0.12.42", - "source": { - "type": "git", - "url": "https://github.com/swaggest/php-json-schema.git", - "reference": "d23adb53808b8e2da36f75bc0188546e4cbe3b45" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swaggest/php-json-schema/zipball/d23adb53808b8e2da36f75bc0188546e4cbe3b45", - "reference": "d23adb53808b8e2da36f75bc0188546e4cbe3b45", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=5.4", - "phplang/scope-exit": "^1.0", - "swaggest/json-diff": "^3.8.2", - "symfony/polyfill-mbstring": "^1.19" - }, - "require-dev": { - "phperf/phpunit": "4.8.37" - }, - "suggest": { - "ext-mbstring": "For better performance" - }, - "type": "library", - "autoload": { - "psr-4": { - "Swaggest\\JsonSchema\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Viacheslav Poturaev", - "email": "vearutop@gmail.com" - } - ], - "description": "High definition PHP structures with JSON-schema based validation", - "support": { - "email": "vearutop@gmail.com", - "issues": "https://github.com/swaggest/php-json-schema/issues", - "source": "https://github.com/swaggest/php-json-schema/tree/v0.12.42" - }, - "time": "2023-09-12T14:43:42+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-28T09:04:16+00:00" - } - ], + "content-hash": "1769eb8cdcb42d17f993aa0ef123895b", + "packages": [], "packages-dev": [], "aliases": [], "minimum-stability": "stable", diff --git a/languages/php/example.php b/languages/php/example.php index 7eafcb96a..864a4ca23 100644 --- a/languages/php/example.php +++ b/languages/php/example.php @@ -3,45 +3,101 @@ require_once 'vendor/autoload.php'; $access_token = getenv('ACCESS_TOKEN'); +$state_file = getenv('STATE_FILE'); $organization_id = getenv('ORGANIZATION_ID'); // Configuring the URLS is optional, set them to null to use the default values $api_url = getenv('API_URL'); $identity_url = getenv('IDENTITY_URL'); -$client_settings = new \Bitwarden\Sdk\BitwardenSettings($api_url, $identity_url); +try { + $client_settings = new \Bitwarden\Sdk\BitwardenSettings($api_url, $identity_url); -$bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($client_settings); -$bitwarden_client->access_token_login($access_token); + $bitwarden_client = new \Bitwarden\Sdk\BitwardenClient($client_settings); -// create project -$res = $bitwarden_client->projects->create('php project', $organization_id); -$project_id = $res->id; + $bitwarden_client->auth->login_access_token($access_token, $state_file); -// get project -$res = $bitwarden_client->projects->get($project_id); + // create project + print("Projects:\n"); + $res = $bitwarden_client->projects->create($organization_id, 'php project'); + $project_id = $res->id; + print("\tcreate: '" . $project_id . "'\n\n"); -// list projects -$res = $bitwarden_client->projects->list($organization_id); + // get project + $res = $bitwarden_client->projects->get($project_id); + print("\tget: '" . $res->name . "'\n\n"); -// update project -$res = $bitwarden_client->projects->put($project_id, 'php test awesome', $organization_id); + // list projects + $res = $bitwarden_client->projects->list($organization_id); + print("\tlist:\n"); + foreach ($res->data as $project) { + print("\t\tID: '" . $project->id . "', Name: '" . $project->name . "'\n"); + } + print("\n"); -// create secret -$res = $bitwarden_client->secrets->create("New Key", "hello world", $organization_id, [$project_id], "123"); -$secret_id = $res->id; + // update project + $res = $bitwarden_client->projects->update($organization_id, $project_id, 'php test project'); + print("\tupdate: '" . $res->name . "'\n\n"); -// get secret -$res = $bitwarden_client->secrets->get($secret_id); + // sync secrets + print("Secrets:\n"); + print("\tSyncing secrets...\n"); + $res = $bitwarden_client->secrets->sync($organization_id, null); + $now = new DateTime(); + $now_string = $now->format('Y-m-d\TH:i:s.u\Z'); + print("\t\tSync has changes: " . ($res->hasChanges ? 'true' : 'false') . "\n\n"); -// list secrets -$res = $bitwarden_client->secrets->list($organization_id); + print("\tSyncing again to ensure no changes since last sync...\n"); + $res = $bitwarden_client->secrets->sync($organization_id, $now_string); + print("\t\tSync has changes: " . ($res->hasChanges ? 'true' : 'false') . "\n\n"); -// update secret -$res = $bitwarden_client->secrets->update($secret_id, "hello world 2", "hello", $organization_id, [$project_id], "123"); + // create secret + $res = $bitwarden_client->secrets->create($organization_id, "New Key", "New value", "New note", [$project_id]); + $secret_id = $res->id; + print("\tcreate: '" . $secret_id . "'\n\n"); -// delete secret -$res = $bitwarden_client->secrets->delete([$secret_id]); + // get secret + $res = $bitwarden_client->secrets->get($secret_id); + print("\tget: '" . $res->key . "'\n\n"); -// delete project -$res = $bitwarden_client->projects->delete([$project_id]); + // get multiple secrets by ids + $res = $bitwarden_client->secrets->get_by_ids([$secret_id]); + print("\tget_by_ids:\n"); + foreach ($res->data as $secret) { + print("\t\tID: '" . $secret->id . "', Key: '" . $secret->key . "'\n"); + } + print("\n"); + + // list secrets + $res = $bitwarden_client->secrets->list($organization_id); + print("\tlist:\n"); + foreach ($res->data as $secret) { + print("\t\tID: '" . $secret->id . "', Key: '" . $secret->key . "'\n"); + } + print("\n"); + + // update secret + $res = $bitwarden_client->secrets->update($organization_id, $secret_id, "Updated key", "Updated value", "Updated note", [$project_id]); + print("\tupdate: '" . $res->key . "'\n\n"); + + // delete secret + print("Cleaning up secrets and projects:\n"); + $res = $bitwarden_client->secrets->delete([$secret_id]); + print("\tdelete:\n"); + foreach ($res->data as $secret) { + print("\t\tdeleted secret: '" . $secret->id . "'\n"); + } + print("\n"); + + // delete project + $res = $bitwarden_client->projects->delete([$project_id]); + print("\tdelete:\n"); + foreach ($res->data as $project) { + print("\t\tdeleted project: '" . $project->id . "'\n"); + } + print("\n"); + +} catch (Exception $e) { + print("Error: " . $e->getMessage() . "\n"); + exit(1); +} diff --git a/languages/php/src/AuthClient.php b/languages/php/src/AuthClient.php new file mode 100644 index 000000000..449c76905 --- /dev/null +++ b/languages/php/src/AuthClient.php @@ -0,0 +1,35 @@ +commandRunner = $commandRunner; + } + + /** + * @throws Exception + */ + public function login_access_token(string $access_token, ?string $state_file): void + { + $access_token_request = new AccessTokenLoginRequest($access_token, $state_file); + $command = new Command(passwordLogin: null, apiKeyLogin: null, loginAccessToken: $access_token_request, + getUserApiKey: null, fingerprint: null, sync: null, secrets: null, projects: null, generators: null); + try { + $result = $this->commandRunner->run($command); + if (!isset($result->authenticated) || !$result->authenticated) { + throw new Exception("Unauthorized"); + } + } catch (Exception $exception) { + throw new Exception("Authorization error: " . $exception->getMessage()); + } + } +} diff --git a/languages/php/src/BitwardenClient.php b/languages/php/src/BitwardenClient.php index 79fccdf9c..c125b6aa7 100644 --- a/languages/php/src/BitwardenClient.php +++ b/languages/php/src/BitwardenClient.php @@ -2,59 +2,40 @@ namespace Bitwarden\Sdk; -use Bitwarden\Sdk\Schemas\AccessTokenLoginRequest; -use Bitwarden\Sdk\schemas\ClientSettings; -use Bitwarden\Sdk\Schemas\Command; -use FFI; -use Swaggest\JsonDiff\Exception; - +use Bitwarden\Sdk\Schemas\ClientSettings; +use Bitwarden\Sdk\Schemas\DeviceType; +use JsonException; class BitwardenClient { - private BitwardenLib $bitwarden_lib; - - private ClientSettings $clientSettings; - public ProjectsClient $projects; public SecretsClient $secrets; - private CommandRunner $commandRunner; + public AuthClient $auth; - private FFI\CData $handle; + private BitwardenLib $bitwarden_lib; + private ClientSettings $clientSettings; + + private CommandRunner $commandRunner; + + /** + * @throws JsonException + */ public function __construct(BitwardenSettings $bitwardenSettings) { - $this->clientSettings = new ClientSettings(); - $this->clientSettings->apiUrl = $bitwardenSettings->get_api_url(); - $this->clientSettings->identityUrl = $bitwardenSettings->get_identity_url(); - $this->clientSettings->userAgent = "Bitwarden PHP-SDK"; + $this->clientSettings = new ClientSettings(apiUrl: $bitwardenSettings->get_api_url(), + deviceType: DeviceType::$SDK, identityUrl: $bitwardenSettings->get_identity_url(), + userAgent: "Bitwarden PHP-SDK"); $this->bitwarden_lib = new BitwardenLib(); - $this->handle = $this->bitwarden_lib->init($this->clientSettings); + $this->bitwarden_lib->init($this->clientSettings); - $this->commandRunner = new CommandRunner($this->bitwarden_lib, $this->handle); + $this->commandRunner = new CommandRunner($this->bitwarden_lib); $this->projects = new ProjectsClient($this->commandRunner); $this->secrets = new SecretsClient($this->commandRunner); - } - - /** - * @throws \Exception - */ - public function access_token_login(string $access_token) - { - $access_token_request = new AccessTokenLoginRequest(); - $access_token_request->accessToken = $access_token; - $command = new Command(); - $command->accessTokenLogin = $access_token_request->jsonSerialize(); - $result = $this->commandRunner->run($command); - if (!isset($result->authenticated)) { - throw new \Exception("Authorization error"); - } - - if ($result->authenticated == False) { - throw new \Exception("Unauthorized"); - } + $this->auth = new AuthClient($this->commandRunner); } public function __destruct() diff --git a/languages/php/src/BitwardenLib.php b/languages/php/src/BitwardenLib.php index 351728986..53be3299b 100644 --- a/languages/php/src/BitwardenLib.php +++ b/languages/php/src/BitwardenLib.php @@ -4,10 +4,11 @@ use Bitwarden\Sdk\Schemas\ClientSettings; use Bitwarden\Sdk\Schemas\Command; +use Exception; use FFI; -use Swaggest\JsonDiff\Exception; -use Swaggest\JsonSchema\JsonSchema; - +use JsonException; +use RuntimeException; +use stdClass; class BitwardenLib { @@ -15,36 +16,36 @@ class BitwardenLib public FFI\CData $handle; /** - * @throws \Exception + * @throws Exception */ public function __construct() { $lib_file = null; if (PHP_OS === 'WINNT') { - $lib_file = '/lib/windows-x64/bitwarden_c.dll'; - if (file_exists($lib_file) == false) { - $lib_file = __DIR__.'/../../../target/debug/bitwarden_c.dll'; + $lib_file = __DIR__ . '/lib/windows-x64/bitwarden_c.dll'; + if (!file_exists($lib_file)) { + $lib_file = __DIR__ . '/../../../target/debug/bitwarden_c.dll'; } } elseif (PHP_OS === 'Linux') { - $lib_file = '/lib/linux-x64/libbitwarden_c.so'; - if (file_exists($lib_file) == false) { - $lib_file = __DIR__.'/../../../target/debug/libbitwarden_c.so'; + $lib_file = __DIR__ . '/lib/linux-x64/libbitwarden_c.so'; + if (!file_exists($lib_file)) { + $lib_file = __DIR__ . '/../../../target/debug/libbitwarden_c.so'; } } elseif (PHP_OS === 'Darwin') { $architecture = trim(exec('uname -m')); if ($architecture === 'x86_64' || $architecture === 'amd64') { - $lib_file = __DIR__.'/lib/macos-x64/libbitwarden_c.dylib'; + $lib_file = __DIR__ . '/lib/macos-x64/libbitwarden_c.dylib'; } elseif ($architecture === 'arm64') { - $lib_file = __DIR__.'/lib/macos-arm64/libbitwarden_c.dylib'; + $lib_file = __DIR__ . '/lib/macos-arm64/libbitwarden_c.dylib'; } - if (file_exists($lib_file) == false) { - $lib_file = __DIR__.'/../../../target/debug/libbitwarden_c.dylib'; + if (!file_exists($lib_file)) { + $lib_file = __DIR__ . '/../../../target/debug/libbitwarden_c.dylib'; } } - if ($lib_file == null || is_file($lib_file) == false) { - throw new \Exception("Lib file not found"); + if ($lib_file == null || !is_file($lib_file)) { + throw new Exception("Lib file not found"); } $this->ffi = FFI::cdef(' @@ -55,20 +56,29 @@ public function __construct() ); } + /** + * @throws JsonException + * @throws Exception + */ public function init(ClientSettings $client_settings): FFI\CData { - $this->handle = $this->ffi->init(json_encode($client_settings->jsonSerialize())); + $encoded_json = $this::json_encode_sdk_format($client_settings->to()); + $this->handle = $this->ffi->init($encoded_json); return $this->handle; } - public function run_command(Command $command): \stdClass + /** + * @throws JsonException + * @throws Exception + */ + public function run_command(Command $command): stdClass { - $encoded_json = json_encode($command->jsonSerialize()); + $encoded_json = $this::json_encode_sdk_format($command->to()); try { $result = $this->ffi->run_command($encoded_json, $this->handle); return json_decode(FFI::string($result)); - } catch (\FFI\Exception $e) { - throw new \RuntimeException('Error occurred during FFI operation: ' . $e->getMessage()); + } catch (FFI\Exception $e) { + throw new RuntimeException('Error occurred during FFI operation: ' . $e->getMessage()); } } @@ -76,4 +86,27 @@ public function free_mem(): void { $this->ffi->free_mem($this->handle); } + + /** + * @throws JsonException + */ + private static function json_encode_sdk_format($object): string + { + $withoutNull = function ($a) use (&$withoutNull) { + if (is_object($a)) { + $a = array_filter((array)$a); + foreach ($a as $k => $v) { + $a[$k] = $withoutNull($v); + } + + return (object)$a; + } + + return $a; + }; + + $object_no_nulls = $withoutNull($object); + + return json_encode($object_no_nulls, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES); + } } diff --git a/languages/php/src/CommandRunner.php b/languages/php/src/CommandRunner.php index 9eec68b2d..532b9625e 100644 --- a/languages/php/src/CommandRunner.php +++ b/languages/php/src/CommandRunner.php @@ -2,36 +2,32 @@ namespace Bitwarden\Sdk; - use Bitwarden\Sdk\Schemas\Command; -use FFI; +use Exception; +use stdClass; class CommandRunner { - private FFI\CData $handle; - private BitwardenLib $bitwardenLib; - public function __construct(BitwardenLib $bitwardenLib, $handle) + public function __construct(BitwardenLib $bitwardenLib) { $this->bitwardenLib = $bitwardenLib; - $this->handle = $handle; } /** - * @throws \Exception + * @throws Exception */ - public function run(Command $command): \stdClass + public function run(Command $command): stdClass { $result = $this->bitwardenLib->run_command($command); - if ($result->success == true) { + if ($result->success) { return $result->data; } - if (isset($result->errorMessage)) - { - throw new \Exception($result->errorMessage); + if (isset($result->errorMessage)) { + throw new Exception($result->errorMessage); } - throw new \Exception("Unknown error occurred"); + throw new Exception("Unknown error occurred"); } } diff --git a/languages/php/src/ProjectsClient.php b/languages/php/src/ProjectsClient.php index 6b6f9fb6a..cca44f1e6 100644 --- a/languages/php/src/ProjectsClient.php +++ b/languages/php/src/ProjectsClient.php @@ -9,6 +9,8 @@ use Bitwarden\Sdk\Schemas\ProjectsCommand; use Bitwarden\Sdk\Schemas\ProjectsDeleteRequest; use Bitwarden\Sdk\Schemas\ProjectsListRequest; +use Exception; +use stdClass; class ProjectsClient { @@ -19,63 +21,74 @@ public function __construct(CommandRunner $commandRunner) $this->commandRunner = $commandRunner; } - public function get(string $project_id): \stdClass + /** + * @throws Exception + */ + public function get(string $project_id): stdClass { - $project_get_request = new ProjectGetRequest(); - $project_get_request->id = $project_id; + $project_get_request = new ProjectGetRequest($project_id); $project_get_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->get = $project_get_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: $project_get_request, create: null, list: null, update: null, + delete: null); return $this->run_project_command($project_command); } - public function list(string $organization_id): \stdClass + /** + * @throws Exception + */ + public function list(string $organization_id): stdClass { - $project_list_request = new ProjectsListRequest(); - $project_list_request->organizationId = $organization_id; + $project_list_request = new ProjectsListRequest($organization_id); $project_list_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->list = $project_list_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: null, list: $project_list_request, update: null, + delete: null); return $this->run_project_command($project_command); } - public function create(string $project_name, string $organization_id): \stdClass + /** + * @throws Exception + */ + public function create(string $organization_id, string $project_name): stdClass { - $project_create_request = new ProjectCreateRequest(); - $project_create_request->name = $project_name; - $project_create_request->organizationId = $organization_id; + $project_create_request = new ProjectCreateRequest(name: $project_name, organizationId: $organization_id); $project_create_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->create = $project_create_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: $project_create_request, list: null, update: null, + delete: null); return $this->run_project_command($project_command); } - public function put(string $project_id, string $project_name, string $organization_id): \stdClass + /** + * @throws Exception + */ + public function update(string $organization_id, string $project_id, string $project_name): stdClass { - $project_put_request = new ProjectPutRequest(); - $project_put_request->organizationId = $organization_id; - $project_put_request->name = $project_name; - $project_put_request->id = $project_id; + $project_put_request = new ProjectPutRequest(id: $project_id, name: $project_name, + organizationId: $organization_id); $project_put_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->update = $project_put_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: null, list: null, update: $project_put_request, + delete: null); return $this->run_project_command($project_command); } - public function delete(array $ids): \stdClass + /** + * @throws Exception + */ + public function delete(array $ids): stdClass { - $projects_delete_request = new ProjectsDeleteRequest(); - $projects_delete_request->ids = $ids; + $projects_delete_request = new ProjectsDeleteRequest($ids); $projects_delete_request->validate(); - $project_command = new ProjectsCommand(); - $project_command->delete = $projects_delete_request->jsonSerialize(); + $project_command = new ProjectsCommand(get: null, create: null, list: null, update: null, + delete: $projects_delete_request); return $this->run_project_command($project_command); } - public function run_project_command($projectCommand): \stdClass + /** + * @throws Exception + */ + public function run_project_command($projectCommand): stdClass { - $command = new Command(); - $command->projects = $projectCommand; + $command = new Command(passwordLogin: null, apiKeyLogin: null, loginAccessToken: null, getUserApiKey: null, + fingerprint: null, sync: null, secrets: null, projects: $projectCommand, generators: null); return $this->commandRunner->run($command); } } diff --git a/languages/php/src/SecretsClient.php b/languages/php/src/SecretsClient.php index d5c0b0cef..85bc334d5 100644 --- a/languages/php/src/SecretsClient.php +++ b/languages/php/src/SecretsClient.php @@ -10,6 +10,9 @@ use Bitwarden\Sdk\Schemas\SecretsCommand; use Bitwarden\Sdk\Schemas\SecretsDeleteRequest; use Bitwarden\Sdk\Schemas\SecretsGetRequest; +use Bitwarden\Sdk\Schemas\SecretsSyncRequest; +use Exception; +use stdClass; class SecretsClient { @@ -20,79 +23,103 @@ public function __construct(CommandRunner $commandRunner) $this->commandRunner = $commandRunner; } - public function get(string $secret_id): \stdClass + /** + * @throws Exception + */ + public function get(string $secret_id): stdClass { - $secret_get_request = new SecretGetRequest(); - $secret_get_request->id = $secret_id; + $secret_get_request = new SecretGetRequest($secret_id); $secret_get_request->validate(); - $secret_command = new SecretsCommand(); - $secret_command->get = $secret_get_request->jsonSerialize(); - return $this->run_secret_command($secret_command); + $secrets_command = new SecretsCommand(get: $secret_get_request, getByIds: null, create: null, list: null, + update: null, delete: null, sync: null); + return $this->run_secret_command($secrets_command); } - public function get_by_ids(array $secret_ids): \stdClass + /** + * @throws Exception + */ + public function get_by_ids(array $secret_ids): stdClass { - $project_get_by_ids_request = new SecretsGetRequest(); - $project_get_by_ids_request->ids = $secret_ids; + $project_get_by_ids_request = new SecretsGetRequest($secret_ids); $project_get_by_ids_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->get_by_ids = $project_get_by_ids_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: $project_get_by_ids_request, create: null, list: null, + update: null, delete: null, sync: null); return $this->run_secret_command($secrets_command); } - public function list(string $organization_id): \stdClass + /** + * @throws Exception + */ + public function list(string $organization_id): stdClass { - $secrets_list_request = new SecretIdentifiersRequest(); - $secrets_list_request->organizationId = $organization_id; + $secrets_list_request = new SecretIdentifiersRequest($organization_id); $secrets_list_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->list = $secrets_list_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: $secrets_list_request, + update: null, delete: null, sync: null); return $this->run_secret_command($secrets_command); } - public function create(string $key, string $note, string $organization_id, array $project_ids, string $value): \stdClass + /** + * @throws Exception + */ + public function create(string $organization_id, string $key, string $value, string $note, array $project_ids): stdClass { - $secrets_create_request = new SecretCreateRequest(); - $secrets_create_request->organizationId = $organization_id; - $secrets_create_request->projectIds = $project_ids; - $secrets_create_request->key = $key; - $secrets_create_request->note = $note; - $secrets_create_request->value = $value; + $secrets_create_request = new SecretCreateRequest(key: $key, note: $note, organizationId: $organization_id, + projectIds: $project_ids, value: $value); $secrets_create_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->create = $secrets_create_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: $secrets_create_request, list: null, + update: null, delete: null, sync: null); return $this->run_secret_command($secrets_command); } - public function update(string $id, string $key, string $note, string $organization_id, array $project_ids, string $value): \stdClass + /** + * @throws Exception + */ + public function update(string $organization_id, string $id, string $key, string $value, string $note, array $project_ids): stdClass { - $secrets_put_request = new SecretPutRequest(); - $secrets_put_request->id = $id; - $secrets_put_request->organizationId = $organization_id; - $secrets_put_request->projectIds = $project_ids; - $secrets_put_request->key = $key; - $secrets_put_request->note = $note; - $secrets_put_request->value = $value; + $secrets_put_request = new SecretPutRequest(id: $id, key: $key, note: $note, organizationId: $organization_id, + projectIds: $project_ids, value: $value); $secrets_put_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->update = $secrets_put_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: null, + update: $secrets_put_request, delete: null, sync: null); return $this->run_secret_command($secrets_command); } - public function delete(array $secrets_ids): \stdClass + /** + * @throws Exception + */ + public function delete(array $secrets_ids): stdClass { - $secrets_delete_request = new SecretsDeleteRequest(); - $secrets_delete_request->ids = $secrets_ids; + $secrets_delete_request = new SecretsDeleteRequest($secrets_ids); $secrets_delete_request->validate(); - $secrets_command = new SecretsCommand(); - $secrets_command->delete = $secrets_delete_request->jsonSerialize(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: null, + update: null, delete: $secrets_delete_request, sync: null); + return $this->run_secret_command($secrets_command); + } + + /** + * @throws Exception + */ + public function sync(string $organization_id, ?string $last_synced_date): stdClass + { + if (empty($last_synced_date)) { + $last_synced_date = "1970-01-01T00:00:00.000Z"; + } + + $secrets_sync_request = new SecretsSyncRequest(lastSyncedDate: $last_synced_date, organizationId: $organization_id); + $secrets_sync_request->validate(); + $secrets_command = new SecretsCommand(get: null, getByIds: null, create: null, list: null, + update: null, delete: null, sync: $secrets_sync_request); return $this->run_secret_command($secrets_command); } - public function run_secret_command($secretsCommand): \stdClass + /** + * @throws Exception + */ + public function run_secret_command($secretsCommand): stdClass { - $command = new Command(); - $command->secrets = $secretsCommand; + $command = new Command(passwordLogin: null, apiKeyLogin: null, loginAccessToken: null, getUserApiKey: null, + fingerprint: null, sync: null, secrets: $secretsCommand, projects: null, generators: null); return $this->commandRunner->run($command); } } diff --git a/languages/php/src/schemas/AccessTokenLoginRequest.php b/languages/php/src/schemas/AccessTokenLoginRequest.php deleted file mode 100644 index a08805f92..000000000 --- a/languages/php/src/schemas/AccessTokenLoginRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -accessToken = Schema::string(); - $properties->accessToken->description = "Bitwarden service API access token"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->description = "Login to Bitwarden with access token"; - $ownerSchema->required = array( - self::names()->accessToken, - ); - $ownerSchema->setFromRef('#/definitions/AccessTokenLoginRequest'); - } -} diff --git a/languages/php/src/schemas/BitwardenClassStructure.php b/languages/php/src/schemas/BitwardenClassStructure.php deleted file mode 100644 index fd50354d4..000000000 --- a/languages/php/src/schemas/BitwardenClassStructure.php +++ /dev/null @@ -1,11 +0,0 @@ -properties = $properties; - $schema->objectItemClass = $className; - $schemaWrapper = new Wrapper($schema); - static::setUpProperties($properties, $schema); - if (null === $schema->getFromRefs()) { - $schema->setFromRef('#/definitions/' . $className); - } - if ($properties->isEmpty()) { - $schema->properties = null; - } - $properties->lock(); - } - - return $schemaWrapper; - } - - /** - * @return Properties|static|null - */ - public static function properties() - { - return static::schema()->getProperties(); - } - - /** - * @param mixed $data - * @param Context $options - * @return static|mixed - * @throws \Swaggest\JsonSchema\Exception - * @throws \Swaggest\JsonSchema\InvalidValue - */ - public static function import($data, Context $options = null) - { - return static::schema()->in($data, $options); - } - - /** - * @param mixed $data - * @param Context $options - * @return mixed - * @throws \Swaggest\JsonSchema\InvalidValue - * @throws \Exception - */ - public static function export($data, Context $options = null) - { - return static::schema()->out($data, $options); - } - - /** - * @param ObjectItemContract $objectItem - * @return static - */ - public static function pick(ObjectItemContract $objectItem) - { - $className = get_called_class(); - return $objectItem->getNestedObject($className); - } - - /** - * @return static - */ - public static function create() - { - return new static; - } - - protected $__validateOnSet = true; // todo skip validation during import - - /** - * @return \stdClass - */ - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - $result = new \stdClass(); - $schema = static::schema(); - $properties = $schema->getProperties(); - $processed = array(); - if (null !== $properties) { - foreach ($properties->getDataKeyMap() as $propertyName => $dataName) { - $value = $this->$propertyName ?? null; - - // Value is exported if exists. - if (null !== $value || array_key_exists($propertyName, $this->__arrayOfData)) { - $result->$dataName = $value; - $processed[$propertyName] = true; - continue; - } - - // Non-existent value is only exported if belongs to nullable property (having 'null' in type array). - $property = $schema->getProperty($propertyName); - if ($property instanceof Schema) { - $types = $property->type; - if ($types === Schema::NULL || (is_array($types) && in_array(Schema::NULL, $types))) { - $result->$dataName = $value; - } - } - } - } - foreach ($schema->getNestedPropertyNames() as $name) { - /** @var ObjectItem $nested */ - $nested = $this->$name; - if (null !== $nested) { - foreach ((array)$nested->jsonSerialize() as $key => $value) { - $result->$key = $value; - } - } - } - - if (!empty($this->__arrayOfData)) { - foreach ($this->__arrayOfData as $name => $value) { - if (!isset($processed[$name])) { - $result->$name = $this->{$name}; - } - } - } - - return $result; - } - - /** - * @return static|NameMirror - */ - public static function names(Properties $properties = null, $mapping = Schema::DEFAULT_MAPPING) - { - if ($properties !== null) { - return new NameMirror($properties->getDataKeyMap($mapping)); - } - - static $nameflector = null; - if (null === $nameflector) { - $nameflector = new NameMirror(); - } - return $nameflector; - } - - public function __set($name, $column) // todo nested schemas - { - if ($this->__validateOnSet) { - if ($property = static::schema()->getProperty($name)) { - $property->out($column); - } - } - $this->__arrayOfData[$name] = $column; - return $this; - } - - public static function className() - { - return get_called_class(); - } - - /** - * @throws \Exception - * @throws \Swaggest\JsonSchema\InvalidValue - */ - public function validate() - { - static::schema()->out($this); - } -} - diff --git a/languages/php/src/schemas/ClientSettings.php b/languages/php/src/schemas/ClientSettings.php deleted file mode 100644 index c27cc3322..000000000 --- a/languages/php/src/schemas/ClientSettings.php +++ /dev/null @@ -1,133 +0,0 @@ -identityUrl = Schema::string(); - $properties->identityUrl->description = "The identity url of the targeted Bitwarden instance. Defaults to `https://identity.bitwarden.com`"; - $properties->identityUrl->default = "https://identity.bitwarden.com"; - $properties->apiUrl = Schema::string(); - $properties->apiUrl->description = "The api url of the targeted Bitwarden instance. Defaults to `https://api.bitwarden.com`"; - $properties->apiUrl->default = "https://api.bitwarden.com"; - $properties->userAgent = Schema::string(); - $properties->userAgent->description = "The user_agent to sent to Bitwarden. Defaults to `Bitwarden Rust-SDK`"; - $properties->userAgent->default = "Bitwarden Rust-SDK"; - $properties->deviceType = new Schema(); - $propertiesDeviceTypeAllOf0 = Schema::string(); - $propertiesDeviceTypeAllOf0->enum = array( - self::ANDROID, - self::I_OS, - self::CHROME_EXTENSION, - self::FIREFOX_EXTENSION, - self::OPERA_EXTENSION, - self::EDGE_EXTENSION, - self::WINDOWS_DESKTOP, - self::MAC_OS_DESKTOP, - self::LINUX_DESKTOP, - self::CHROME_BROWSER, - self::FIREFOX_BROWSER, - self::OPERA_BROWSER, - self::EDGE_BROWSER, - self::IE_BROWSER, - self::UNKNOWN_BROWSER, - self::ANDROID_AMAZON, - self::UWP, - self::SAFARI_BROWSER, - self::VIVALDI_BROWSER, - self::VIVALDI_EXTENSION, - self::SAFARI_EXTENSION, - self::SDK, - ); - $propertiesDeviceTypeAllOf0->setFromRef('#/definitions/DeviceType'); - $properties->deviceType->allOf[0] = $propertiesDeviceTypeAllOf0; - $properties->deviceType->description = "Device type to send to Bitwarden. Defaults to SDK"; - $properties->deviceType->default = "SDK"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->schema = "http://json-schema.org/draft-07/schema#"; - $ownerSchema->title = "ClientSettings"; - $ownerSchema->description = "Basic client behavior settings. These settings specify the various targets and behavior of the Bitwarden Client. They are optional and uneditable once the client is initialized.\n\nDefaults to\n\n``` # use bitwarden::client::client_settings::{ClientSettings, DeviceType}; # use assert_matches::assert_matches; let settings = ClientSettings { identity_url: \"https://identity.bitwarden.com\".to_string(), api_url: \"https://api.bitwarden.com\".to_string(), user_agent: \"Bitwarden Rust-SDK\".to_string(), device_type: DeviceType::SDK, }; let default = ClientSettings::default(); assert_matches!(settings, default); ```\n\nTargets `localhost:8080` for debug builds."; - } -} diff --git a/languages/php/src/schemas/Command.php b/languages/php/src/schemas/Command.php deleted file mode 100644 index cbd649c2f..000000000 --- a/languages/php/src/schemas/Command.php +++ /dev/null @@ -1,44 +0,0 @@ -projects = ProjectsCommand::schema(); - $properties->secrets = SecretsCommand::schema(); - $properties->accessTokenLogin = AccessTokenLoginRequest::schema(); - - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - - $ownerSchema->oneOf = array( - self::names()->projects, - self::names()->secrets, - self::names()->accessTokenLogin, - ); - } -} diff --git a/languages/php/src/schemas/ProjectCreateRequest.php b/languages/php/src/schemas/ProjectCreateRequest.php deleted file mode 100644 index 6a4e0f082..000000000 --- a/languages/php/src/schemas/ProjectCreateRequest.php +++ /dev/null @@ -1,43 +0,0 @@ -organizationId = Schema::string(); - $properties->organizationId->description = "Organization where the project will be created"; - $properties->organizationId->format = "uuid"; - $properties->name = Schema::string(); - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->name, - self::names()->organizationId, - ); - $ownerSchema->setFromRef('#/definitions/ProjectCreateRequest'); - } -} diff --git a/languages/php/src/schemas/ProjectGetRequest.php b/languages/php/src/schemas/ProjectGetRequest.php deleted file mode 100644 index 972bf18ec..000000000 --- a/languages/php/src/schemas/ProjectGetRequest.php +++ /dev/null @@ -1,37 +0,0 @@ -id = Schema::string(); - $properties->id->description = "ID of the project to retrieve"; - $properties->id->format = "uuid"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->id, - ); - $ownerSchema->setFromRef('#/definitions/ProjectGetRequest'); - } -} diff --git a/languages/php/src/schemas/ProjectPutRequest.php b/languages/php/src/schemas/ProjectPutRequest.php deleted file mode 100644 index 96b9705e7..000000000 --- a/languages/php/src/schemas/ProjectPutRequest.php +++ /dev/null @@ -1,50 +0,0 @@ -id = Schema::string(); - $properties->id->description = "ID of the project to modify"; - $properties->id->format = "uuid"; - $properties->organizationId = Schema::string(); - $properties->organizationId->description = "Organization ID of the project to modify"; - $properties->organizationId->format = "uuid"; - $properties->name = Schema::string(); - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->id, - self::names()->name, - self::names()->organizationId, - ); - $ownerSchema->setFromRef('#/definitions/ProjectPutRequest'); - } -} diff --git a/languages/php/src/schemas/ProjectsCommand.php b/languages/php/src/schemas/ProjectsCommand.php deleted file mode 100644 index 22645db3c..000000000 --- a/languages/php/src/schemas/ProjectsCommand.php +++ /dev/null @@ -1,55 +0,0 @@ - Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the projects whose IDs match the provided ones - * - * Returns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse) - */ -class ProjectsCommand extends BitwardenClassStructure -{ - public ?\stdClass $delete; - - public ?\stdClass $get; - - public ?\stdClass $list; - - public ?\stdClass $create; - - public ?\stdClass $update; - - - /** - * @param Properties|static $properties - * @param Schema $ownerSchema - */ - public static function setUpProperties($properties, Schema $ownerSchema) - { - $properties->delete = ProjectsDeleteRequest::schema() ? ProjectsDeleteRequest::schema() : null; - $properties->get = ProjectGetRequest::schema() ? ProjectGetRequest::schema() : null; - $properties->list = ProjectsListRequest::schema() ? ProjectsListRequest::schema() : null; - $properties->update = ProjectPutRequest::schema() ? ProjectPutRequest::schema() : null; - $properties->create = ProjectCreateRequest::schema() ? ProjectCreateRequest::schema() : null; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->description = "> Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the projects whose IDs match the provided ones\n\nReturns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse)"; - - $ownerSchema->oneOf = array( - self::names()->create, - self::names()->delete, - self::names()->get, - self::names()->list, - self::names()->update, - ); - } -} diff --git a/languages/php/src/schemas/ProjectsDeleteRequest.php b/languages/php/src/schemas/ProjectsDeleteRequest.php deleted file mode 100644 index 87a7cfad7..000000000 --- a/languages/php/src/schemas/ProjectsDeleteRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -ids = Schema::arr(); - $properties->ids->items = Schema::string(); - $properties->ids->items->format = "uuid"; - $properties->ids->description = "IDs of the projects to delete"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->ids, - ); - $ownerSchema->setFromRef('#/definitions/ProjectsDeleteRequest'); - } -} diff --git a/languages/php/src/schemas/ProjectsListRequest.php b/languages/php/src/schemas/ProjectsListRequest.php deleted file mode 100644 index cc1a9474f..000000000 --- a/languages/php/src/schemas/ProjectsListRequest.php +++ /dev/null @@ -1,38 +0,0 @@ -organizationId = Schema::string(); - $properties->organizationId->description = "Organization to retrieve all the projects from"; - $properties->organizationId->format = "uuid"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->organizationId, - ); - $ownerSchema->setFromRef('#/definitions/ProjectsListRequest'); - } -} diff --git a/languages/php/src/schemas/SecretCreateRequest.php b/languages/php/src/schemas/SecretCreateRequest.php deleted file mode 100644 index d34b36e98..000000000 --- a/languages/php/src/schemas/SecretCreateRequest.php +++ /dev/null @@ -1,58 +0,0 @@ -organizationId = Schema::string(); - $properties->organizationId->description = "Organization where the secret will be created"; - $properties->organizationId->format = "uuid"; - $properties->key = Schema::string(); - $properties->value = Schema::string(); - $properties->note = Schema::string(); - $properties->projectIds = (new Schema())->setType([Schema::_ARRAY, Schema::NULL]); - $properties->projectIds->items = Schema::string(); - $properties->projectIds->items->format = "uuid"; - $properties->projectIds->description = "IDs of the projects that this secret will belong to"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->key, - self::names()->note, - self::names()->organizationId, - self::names()->value, - ); - $ownerSchema->setFromRef('#/definitions/SecretCreateRequest'); - } -} diff --git a/languages/php/src/schemas/SecretGetRequest.php b/languages/php/src/schemas/SecretGetRequest.php deleted file mode 100644 index f31f7cad3..000000000 --- a/languages/php/src/schemas/SecretGetRequest.php +++ /dev/null @@ -1,38 +0,0 @@ -id = Schema::string(); - $properties->id->description = "ID of the secret to retrieve"; - $properties->id->format = "uuid"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->id, - ); - $ownerSchema->setFromRef('#/definitions/SecretGetRequest'); - } -} diff --git a/languages/php/src/schemas/SecretIdentifiersRequest.php b/languages/php/src/schemas/SecretIdentifiersRequest.php deleted file mode 100644 index b4e75b801..000000000 --- a/languages/php/src/schemas/SecretIdentifiersRequest.php +++ /dev/null @@ -1,38 +0,0 @@ -organizationId = Schema::string(); - $properties->organizationId->description = "Organization to retrieve all the secrets from"; - $properties->organizationId->format = "uuid"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->organizationId, - ); - $ownerSchema->setFromRef('#/definitions/SecretIdentifiersRequest'); - } -} diff --git a/languages/php/src/schemas/SecretPutRequest.php b/languages/php/src/schemas/SecretPutRequest.php deleted file mode 100644 index d890a909d..000000000 --- a/languages/php/src/schemas/SecretPutRequest.php +++ /dev/null @@ -1,64 +0,0 @@ -id = Schema::string(); - $properties->id->description = "ID of the secret to modify"; - $properties->id->format = "uuid"; - $properties->organizationId = Schema::string(); - $properties->organizationId->description = "Organization ID of the secret to modify"; - $properties->organizationId->format = "uuid"; - $properties->key = Schema::string(); - $properties->value = Schema::string(); - $properties->note = Schema::string(); - $properties->projectIds = (new Schema())->setType([Schema::_ARRAY, Schema::NULL]); - $properties->projectIds->items = Schema::string(); - $properties->projectIds->items->format = "uuid"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->id, - self::names()->key, - self::names()->note, - self::names()->organizationId, - self::names()->value, - ); - $ownerSchema->setFromRef('#/definitions/SecretPutRequest'); - } -} diff --git a/languages/php/src/schemas/SecretVerificationRequest.php b/languages/php/src/schemas/SecretVerificationRequest.php deleted file mode 100644 index 95cfd1e15..000000000 --- a/languages/php/src/schemas/SecretVerificationRequest.php +++ /dev/null @@ -1,35 +0,0 @@ -masterPassword = (new Schema())->setType([Schema::STRING, Schema::NULL]); - $properties->masterPassword->description = "The user's master password to use for user verification. If supplied, this will be used for verification purposes."; - $properties->otp = (new Schema())->setType([Schema::STRING, Schema::NULL]); - $properties->otp->description = "Alternate user verification method through OTP. This is provided for users who have no master password due to use of Customer Managed Encryption. Must be present and valid if master_password is absent."; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->setFromRef('#/definitions/SecretVerificationRequest'); - } -} diff --git a/languages/php/src/schemas/SecretsCommand.php b/languages/php/src/schemas/SecretsCommand.php deleted file mode 100644 index 1ed8c97c5..000000000 --- a/languages/php/src/schemas/SecretsCommand.php +++ /dev/null @@ -1,56 +0,0 @@ - Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the secrets whose IDs match the provided ones - * - * Returns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse) - */ -class SecretsCommand extends BitwardenClassStructure -{ - public ?\stdClass $delete; - - public ?\stdClass $get; - - public ?\stdClass $getByIds; - - public ?\stdClass $list; - - public ?\stdClass $create; - - public ?\stdClass $put; - - /** - * @param Properties|static $properties - * @param Schema $ownerSchema - */ - public static function setUpProperties($properties, Schema $ownerSchema) - { - $properties->delete = SecretsDeleteRequest::schema() ? SecretsDeleteRequest::schema() : null; - $properties->getByIds = SecretsGetRequest::schema() ? SecretGetRequest::schema() : null; - $properties->create = SecretCreateRequest::schema() ? SecretCreateRequest::schema() : null; - $properties->put = SecretPutRequest::schema() ? SecretPutRequest::schema() : null; - $properties->list = SecretIdentifiersRequest::schema() ? SecretIdentifiersRequest::schema() : null; - $properties->get = SecretsGetRequest::schema() ? SecretGetRequest::schema() : null; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->description = "> Requires Authentication > Requires using an Access Token for login or calling Sync at least once Deletes all the secrets whose IDs match the provided ones\n\nReturns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse)"; - $ownerSchema->oneOf = array( - self::names()->create, - self::names()->put, - self::names()->list, - self::names()->getByIds, - self::names()->delete, - ); - } -} diff --git a/languages/php/src/schemas/SecretsDeleteRequest.php b/languages/php/src/schemas/SecretsDeleteRequest.php deleted file mode 100644 index 35138fcb1..000000000 --- a/languages/php/src/schemas/SecretsDeleteRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -ids = Schema::arr(); - $properties->ids->items = Schema::string(); - $properties->ids->items->format = "uuid"; - $properties->ids->description = "IDs of the secrets to delete"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->ids, - ); - $ownerSchema->setFromRef('#/definitions/SecretsDeleteRequest'); - } -} diff --git a/languages/php/src/schemas/SecretsGetRequest.php b/languages/php/src/schemas/SecretsGetRequest.php deleted file mode 100644 index 4758dabf4..000000000 --- a/languages/php/src/schemas/SecretsGetRequest.php +++ /dev/null @@ -1,39 +0,0 @@ -ids = Schema::arr(); - $properties->ids->items = Schema::string(); - $properties->ids->items->format = "uuid"; - $properties->ids->description = "IDs of the secrets to retrieve"; - $ownerSchema->type = Schema::OBJECT; - $ownerSchema->additionalProperties = false; - $ownerSchema->required = array( - self::names()->ids, - ); - $ownerSchema->setFromRef('#/definitions/SecretsGetRequest'); - } -} diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index 5ea71408c..3eaad1903 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -117,9 +117,30 @@ async function main() { java.forEach((file, path) => { writeToFile(javaDir + path, file.lines); }); + + const php = await quicktype({ + inputData, + lang: "php", + inferUuids: false, + inferDateTimes: false, + rendererOptions: { + "acronym-style": "camel", + "with-get": false, + }, + }); + + const phpDir = "./languages/php/src/Schemas/"; + if (!fs.existsSync(phpDir)) { + fs.mkdirSync(phpDir); + } + + php.lines.splice(1, 0, "namespace Bitwarden\\Sdk\\Schemas;", "use stdClass;", "use Exception;"); + + writeToFile("./languages/php/src/Schemas/Schemas.php", php.lines); } main(); + function writeToFile(filename: string, lines: string[]) { const output = fs.createWriteStream(filename); lines.forEach((line) => {