diff --git a/.github/workflows/build-cpp.yml b/.github/workflows/build-cpp.yml index e95e6cbf2..606cefdee 100644 --- a/.github/workflows/build-cpp.yml +++ b/.github/workflows/build-cpp.yml @@ -30,8 +30,11 @@ jobs: - os: macos-13 target: x86_64-apple-darwin - # - os: windows-2022 - # target: x86_64-pc-windows-msvc + - os: macos-13 + target: aarch64-apple-darwin + + - os: windows-2022 + target: x86_64-pc-windows-msvc - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu @@ -52,16 +55,6 @@ jobs: brew install nlohmann-json brew install boost - - name: Cache vcpkg - if: runner.os == 'Windows' - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - with: - path: C:/vcpkg/ - key: vcpkg-${{ runner.os }}-${{ matrix.settings.target }} - restore-keys: | - vcpkg-${{ runner.os }}- - vcpkg- - - name: Export GitHub Actions cache environment variables if: runner.os == 'Windows' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 @@ -70,22 +63,6 @@ jobs: core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Install libraries for Windows - if: runner.os == 'Windows' && steps.cache.outputs.cache-hit != 'true' - env: - VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" - run: | - vcpkg install boost --binarysource="clear;x-gha,readwrite" - vcpkg install nlohmann-json --binarysource="clear;x-gha,readwrite" - shell: pwsh - - - name: Save cache - if: runner.os == 'Windows' - uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - with: - path: C:/vcpkg/ - key: vcpkg-${{ runner.os }}-${{ matrix.settings.target }} - - name: Download schemas uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: @@ -114,6 +91,20 @@ jobs: Get-Acl languages/cpp/include/* | Format-List + - name: Ensure bitwarden-c is in include folder + working-directory: languages/cpp + shell: bash + run: | + mv include/release/* include/ + if [[ '${{ runner.os }}' == 'macOS' || '${{ runner.os }}' == 'Linux' ]]; then + ls include/libbitwarden_c.* || { echo "Missing libbitwarden_c.*"; exit 1; } + fi + if [[ '${{ runner.os }}' == 'Windows' ]]; then + ls include/bitwarden_c.dll || { echo "Missing bitwarden_c.dll"; exit 1; } + ls include/bitwarden_c.dll.lib || { echo "Missing bitwarden_c.dll.lib"; exit 1; } + fi + rmdir include/release + - name: Build unix working-directory: languages/cpp if: runner.os == 'macOS' || runner.os == 'Linux' @@ -139,24 +130,28 @@ jobs: if: runner.os == 'Windows' working-directory: languages/cpp env: - BOOST_INCLUDE_DIR: C:\vcpkg\installed\x64-windows\include\boost - NLOHMANN_JSON_INCLUDE_DIR: C:\vcpkg\installed\x64-windows\include\nlohmann-json + VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" run: | mkdir build cd build - $DNLOHMANN_PATH="C:\vcpkg\installed\x64-windows\include\nlohmann-json" - $DBOOST_PATH="C:\vcpkg\installed\x64-windows\include\boost" - $DTARGET="include/libbitwarden_c.dll" - cmake .. -DNLOHMANN=$DNLOHMANN_PATH -DBOOST=$DBOOST_PATH -DTARGET="include/libbitwarden_c.dll" -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/languages/cpp/ -DBUILD_TESTING=OFF - cmake --build . + $env:DTARGET="include\bitwarden_c.dll.lib" + cmake .. -DTARGET="$env:DTARGET" -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" + cmake --build . --config Release shell: pwsh - name: Copy artifacts working-directory: languages/cpp/build + shell: bash run: | mkdir artifacts - cp libbitwarden_c.* artifacts - cp libBitwardenClient.* artifacts + if [[ '${{ runner.os }}' == 'macOS' || '${{ runner.os }}' == 'Linux' ]]; then + cp libbitwarden_c.* artifacts + cp libBitwardenClient.* artifacts + fi + if [[ '${{ runner.os }}' == 'Windows' ]]; then + cp */BitwardenClient.* artifacts + cp ../include/bitwarden_c.{lib,dll.lib,dll} artifacts + fi - name: Upload C++ package for ${{ matrix.settings.target }} uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 diff --git a/.github/workflows/publish-php.yml b/.github/workflows/publish-php.yml index 0cf7c59b0..74939ec58 100644 --- a/.github/workflows/publish-php.yml +++ b/.github/workflows/publish-php.yml @@ -1,3 +1,4 @@ +--- name: Publish PHP SDK run-name: Publish PHP SDK ${{ inputs.release_type }} @@ -29,9 +30,9 @@ jobs: - name: Branch check if: ${{ inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'main' branch" echo "===================================" exit 1 fi @@ -123,7 +124,7 @@ jobs: working-directory: sm-sdk-php run: | git add . - git commit -m "Update Go SDK to ${{ github.sha }}" + git commit -m "Update PHP SDK to ${{ github.sha }}" if [[ "${{ inputs.release_type }}" == "Dry Run" ]]; then echo "===================================" @@ -135,7 +136,7 @@ jobs: git push origin main fi - - name: Create release tag on SDK Go repo + - name: Create release tag on PHP SDK repo if: ${{ inputs.release_type != 'Dry Run' }} working-directory: sm-sdk-php run: | @@ -177,7 +178,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-apple-darwin skip_unpack: true @@ -186,7 +187,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-aarch64-apple-darwin skip_unpack: true @@ -195,7 +196,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-unknown-linux-gnu skip_unpack: true @@ -204,7 +205,7 @@ jobs: with: workflow: build-rust-cross-platform.yml workflow_conclusion: success - branch: ${{ inputs.release_type == 'Dry Run' && 'main' || github.ref_name }} + branch: main artifacts: libbitwarden_c_files-x86_64-pc-windows-msvc skip_unpack: true diff --git a/.gitignore b/.gitignore index b13651d19..4a1d79fca 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ languages/java/src/main/java/com/bitwarden/sdk/schema languages/js/sdk-client/src/schemas.ts languages/python/bitwarden_sdk/schemas.py support/schemas + +# Cmake build files +languages/cpp/cmake-build-debug diff --git a/crates/bitwarden-crypto/src/keys/shareable_key.rs b/crates/bitwarden-crypto/src/keys/shareable_key.rs index 556432aea..c6a44405c 100644 --- a/crates/bitwarden-crypto/src/keys/shareable_key.rs +++ b/crates/bitwarden-crypto/src/keys/shareable_key.rs @@ -3,7 +3,7 @@ use std::pin::Pin; use aes::cipher::typenum::U64; use generic_array::GenericArray; use hmac::Mac; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroizing; use crate::{ keys::SymmetricCryptoKey, @@ -20,18 +20,17 @@ pub fn derive_shareable_key( info: Option<&str>, ) -> SymmetricCryptoKey { // Because all inputs are fixed size, we can unwrap all errors here without issue - let mut res = PbkdfSha256Hmac::new_from_slice(format!("bitwarden-{}", name).as_bytes()) - .expect("hmac new_from_slice should not fail") - .chain_update(secret) - .finalize() - .into_bytes(); + let res = Zeroizing::new( + PbkdfSha256Hmac::new_from_slice(format!("bitwarden-{}", name).as_bytes()) + .expect("hmac new_from_slice should not fail") + .chain_update(secret) + .finalize() + .into_bytes(), + ); let mut key: Pin>> = hkdf_expand(&res, info).expect("Input is a valid size"); - // Zeroize the temporary buffer - res.zeroize(); - SymmetricCryptoKey::try_from(key.as_mut_slice()).expect("Key is a valid size") } diff --git a/languages/cpp/CMakeBuild.md b/languages/cpp/CMakeBuild.md index cb148069e..b75da5c17 100644 --- a/languages/cpp/CMakeBuild.md +++ b/languages/cpp/CMakeBuild.md @@ -3,7 +3,6 @@ ## Introduction Cmake is used to build the C++ Bitwarden client library. Output should be placed in the build directory. - The output contains two dynamic libraries: - The C++ client `BitwardenClient` @@ -20,7 +19,8 @@ See how to use these libraries in the [example use guide](./examples/ExampleUse. ## Build Commands -One should be in the root directory of the C++ wrapper (the same level where is CMakeLists.txt placed). Paths of the three libraries should be placed inside the cmake build command: +One should be in the root directory of the C++ wrapper (the same level where is CMakeLists.txt placed). Paths of the +three libraries should be placed inside the cmake build command: ```bash mkdir -p build @@ -29,6 +29,16 @@ cmake .. -DNLOHMANN=/path/to/include/nlohmann -DBOOST=/path/to/include/boost -DT cmake --build . ``` +## IDE Support + +You may need to manually set the CMake `TARGET` variable for your IDE. For CLion, add the following to the CMake options +settings: + +```bash +# macOS example +-DTARGET=../../target/release/libbitwarden_c.dylib +``` + ## Example ### macOS diff --git a/languages/cpp/CMakeLists.txt b/languages/cpp/CMakeLists.txt index e513a32ed..e6ad7f4f3 100644 --- a/languages/cpp/CMakeLists.txt +++ b/languages/cpp/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(BitwardenClient) set(CMAKE_CXX_STANDARD 20) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) # Set placeholders to be passed from command line set(NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER ${NLOHMANN}) diff --git a/languages/cpp/README.md b/languages/cpp/README.md index 23b59ac76..fb714a20e 100644 --- a/languages/cpp/README.md +++ b/languages/cpp/README.md @@ -22,9 +22,10 @@ bitwardenSettings.set_identity_url(""); ```c++ std::string accessToken = ""; +std::string stateFile = ""; // Optional - argument in BitwardenClient BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); -bitwardenClient.accessTokenLogin(accessToken); +bitwardenClient.loginAccessToken(accessToken, stateFile); ``` ### Create new project @@ -32,6 +33,7 @@ bitwardenClient.accessTokenLogin(accessToken); ```c++ boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(""); ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "TestProject"); +boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); ``` ### List all projects @@ -43,21 +45,19 @@ ProjectsResponse projectResponseList = bitwardenClient.listProjects(organization ### Get project details ```c++ -boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId); ``` ### Update project ```c++ -boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); -ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "TestProjectUpdated"); +ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(organizationUuid, projectId, "TestProjectUpdated"); ``` ### Delete projects ```c++ -SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); +ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.deleteProjects({projectId}); ``` ### Add new secret @@ -66,7 +66,8 @@ SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({sec std::string key = "key"; std::string value = "value"; std::string note = "note"; -SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); +SecretResponse secretResponseCreate = bitwardenClient.createSecret(organizationUuid, key, value, note, {projectId}); +boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); ``` ### List secrets @@ -77,14 +78,28 @@ SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecret ### Get secret details -``` -boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); +```c++ SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); ``` +### Get multiple secrets by ids + +```c++ +std::vector secretIds = {secretId, secretId2}; +SecretsResponse secretsResponseGet = bitwardenClient.getSecrets(secretIds); +``` + ### Update secret + +```c++ +SecretResponse secretResponseUpdate = bitwardenClient.updateSecret(organizationUuid, secretId, "key2", "value2", "note2", {projectId}); +``` + +### Sync secrets + ```c++ -SecretResponse secretResponseUpdate = bitwardenClient.updateSecret(secretId, "key2", "value2", "note2", organizationUuid, {projectId}); +std::chrono::system_clock::time_point lastSyncedDate = std::chrono::system_clock::now(); +SecretsSyncResponse secretsSyncResponse = bitwardenClient.sync(orgnizationUuid, lastSyncedDate); ``` # Delete secrets diff --git a/languages/cpp/examples/Wrapper.cpp b/languages/cpp/examples/Wrapper.cpp index bb53bf0c4..7790adfb0 100644 --- a/languages/cpp/examples/Wrapper.cpp +++ b/languages/cpp/examples/Wrapper.cpp @@ -1,14 +1,18 @@ #include "BitwardenClient.h" #include #include +#include int main() { // Retrieve access token and organization ID from environment variables - const char* accessTokenEnv = std::getenv("ACCESS_TOKEN"); - const char* organizationIdEnv = std::getenv("ORGANIZATION_ID"); + const char *accessTokenEnv = std::getenv("ACCESS_TOKEN"); + const char *organizationIdEnv = std::getenv("ORGANIZATION_ID"); - const char* apiUrl = std::getenv("API_URL"); - const char* identityUrl = std::getenv("IDENTITY_URL"); + // Use optional state file for authentication + const char *stateFile = std::getenv("STATE_FILE"); + + const char *apiUrl = std::getenv("API_URL"); + const char *identityUrl = std::getenv("IDENTITY_URL"); if (!accessTokenEnv || !organizationIdEnv) { std::cerr << "Error: Environment variables ACCESS_TOKEN or ORGANIZATION_ID not set." << std::endl; @@ -18,57 +22,110 @@ int main() { std::string accessToken = accessTokenEnv; std::string organizationId = organizationIdEnv; - // Configuring the URLS is optional, remove them to use the default values + // Configuring the URLS is optional; if unset, use bitwarden.com BitwardenSettings bitwardenSettings; - bitwardenSettings.set_api_url(apiUrl); - bitwardenSettings.set_identity_url(identityUrl); + if (apiUrl != nullptr && identityUrl != nullptr) { + bitwardenSettings.set_api_url(apiUrl); + bitwardenSettings.set_identity_url(identityUrl); + } else { + std::cerr << "Info: API_URL and IDENTITY_URL are not set, using default values..." << std::endl; + } // Create a Bitwarden client instance - BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings); - // // Access token login - bitwardenClient.loginAccessToken(accessToken); - // Organization ID - boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(organizationId); - - // // Create a new project + BitwardenClient bitwardenClient(bitwardenSettings); + + // Access token login + if (stateFile != nullptr) { + bitwardenClient.loginAccessToken(accessToken, stateFile); + } else { + bitwardenClient.loginAccessToken(accessToken); + } + + // Convert organization ID to UUID + boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(organizationId); + + // Create a new project + std::cout << "Projects:\n"; ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "NewTestProject"); boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id()); - + + std::cout << "\tcreateProject: '" << projectResponseCreate.get_name() << "'\n\n"; + // List projects ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid); + std::cout << "\tlistProjects:\n"; + for (const ProjectResponse& project : projectResponseList.get_data()) { + std::cout << "\t\tID: '" << project.get_id() << "', Name: '" << project.get_name() << "'\n"; + } + std::cout << '\n'; // Get project details ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId); + std::cout << "\tgetProject:\n\t\tID: '" << projectResponseGet.get_id() << "', Name: '" << projectResponseGet.get_name() << "'\n\n"; // Update project - ProjectResponse ProjectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "NewTestProject2"); + ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(organizationUuid, projectId, "NewTestProject2"); + std::cout << "\tupdateProject: '" << projectResponseUpdate.get_name() << "'\n\n"; // Secrets std::string key = "key"; std::string value = "value"; std::string note = "note"; + // Sync secrets + std::cout << "Secrets:\n"; + std::cout << "\tSyncing secrets...\n"; + SecretsSyncResponse secretsSyncResponse = bitwardenClient.sync(organizationUuid, {}); + std::chrono::system_clock::time_point lastSyncedDate = std::chrono::system_clock::now(); + std::cout << "\t\tSync has changes: '" << (secretsSyncResponse.get_has_changes() ? "true" : "false") << "'\n\n"; + + std::cout << "\tSyncing again to ensure no changes since last sync...\n"; + secretsSyncResponse = bitwardenClient.sync(organizationUuid, lastSyncedDate); + std::cout << "\t\tSync has changes: '" << (secretsSyncResponse.get_has_changes() ? "true" : "false") << "'\n\n"; + // Create a new secret - SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId}); + SecretResponse secretResponseCreate = bitwardenClient.createSecret(organizationUuid, key, value, note, {projectId}); boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id()); + std::cout << "\tcreateSecret: '" << secretResponseCreate.get_key() << "'\n\n"; + // List secrets SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid); + std::cout << "\tlistSecrets:\n"; + for (const SecretIdentifierResponse& secretIdentifier : secretIdentifiersResponse.get_data()) { + std::cout << "\t\tID: '" << secretIdentifier.get_id() << "'\n"; + } + std::cout << '\n'; // Get secret details SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId); + std::cout << "\tgetSecret: '" << secretResponseGet.get_key() << "'\n\n"; + + // Get secrets by IDs + std::cout << "\tgetSecretsByIds:\n"; + SecretsResponse secretsResponseGetByIds = bitwardenClient.getSecretsByIds({secretId}); + for (const SecretResponse& secret : secretsResponseGetByIds.get_data()) { + std::cout << "\t\tID: '" << secret.get_id() << "', Key: '" << secret.get_key() << "'\n"; + } + std::cout << '\n'; // Update secret - key = "key2"; - value = "value2"; - note = "note2"; - SecretResponse responseForSecretResponseUpdate = bitwardenClient.updateSecret(secretId, key, value, note, organizationUuid, {projectId}); + key = "updated-key"; + value = "updated-value"; + note = "updated-note"; + SecretResponse responseForSecretResponseUpdate = bitwardenClient.updateSecret( + organizationUuid, secretId, key, value, note, {projectId}); + + std::cout << "\tupdateSecret: '" << responseForSecretResponseUpdate.get_key() << "'\n\n"; // Delete secrets + std::cout << "Deleting projects and secrets...\n"; SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId}); + std::cout << "\tdeleteSecrets: '" << secretsDeleteResponse.get_data()[0].get_id() << "'\n\n"; // Delete projects ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.deleteProjects({projectId}); + std::cout << "\tdeleteProjects: '" << projectsDeleteResponse.get_data()[0].get_id() << "'\n\n"; return 0; } diff --git a/languages/cpp/include/BitwardenClient.h b/languages/cpp/include/BitwardenClient.h index a5cf72475..2910c49b7 100644 --- a/languages/cpp/include/BitwardenClient.h +++ b/languages/cpp/include/BitwardenClient.h @@ -9,20 +9,22 @@ class BitwardenClient { public: - BitwardenClient(const BitwardenSettings& bitwardenSettings = BitwardenSettings()); + explicit BitwardenClient(const BitwardenSettings& bitwardenSettings = BitwardenSettings()); ~BitwardenClient(); - - void accessTokenLogin(const std::string& accessToken); + + void loginAccessToken(const std::string& accessToken, const std::string& stateFile = ""); ProjectResponse getProject(const boost::uuids::uuid& id); ProjectResponse createProject(const boost::uuids::uuid& organizationId, const std::string& name); - ProjectResponse updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name); + ProjectResponse updateProject(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& name); ProjectsDeleteResponse deleteProjects(const std::vector& ids); ProjectsResponse listProjects(const boost::uuids::uuid &organizationId); SecretResponse getSecret(const boost::uuids::uuid& id); - SecretResponse createSecret(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); - SecretResponse updateSecret(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretsResponse getSecretsByIds(const std::vector& ids); + SecretResponse createSecret(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); + SecretResponse updateSecret(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); SecretsDeleteResponse deleteSecrets(const std::vector& ids); SecretIdentifiersResponse listSecrets(const boost::uuids::uuid& organizationId); + SecretsSyncResponse sync(const boost::uuids::uuid &organizationId, const std::chrono::system_clock::time_point &lastSyncedDate); private: BitwardenLibrary* library; diff --git a/languages/cpp/include/Projects.h b/languages/cpp/include/Projects.h index 9bef19b9c..27511c327 100644 --- a/languages/cpp/include/Projects.h +++ b/languages/cpp/include/Projects.h @@ -10,7 +10,7 @@ class Projects { ProjectResponse get(const boost::uuids::uuid& id); ProjectResponse create(const boost::uuids::uuid& organizationId, const std::string& name); - ProjectResponse update(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name); + ProjectResponse update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& name); ProjectsDeleteResponse deleteProjects(const std::vector& ids); ProjectsResponse list(const boost::uuids::uuid& organizationId); diff --git a/languages/cpp/include/Secrets.h b/languages/cpp/include/Secrets.h index 024ec3692..5c5a3275c 100644 --- a/languages/cpp/include/Secrets.h +++ b/languages/cpp/include/Secrets.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include "CommandRunner.h" @@ -9,10 +11,12 @@ class Secrets { Secrets(CommandRunner* commandRunner); SecretResponse get(const boost::uuids::uuid& id); - SecretResponse create(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); - SecretResponse update(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds); + SecretsResponse getByIds(const std::vector &ids); + SecretResponse create(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); + SecretResponse update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds); SecretsDeleteResponse deleteSecrets(const std::vector& ids); SecretIdentifiersResponse list(const boost::uuids::uuid& organizationId); + SecretsSyncResponse sync(const boost::uuids::uuid& organizationId, const boost::optional& lastSyncedDate); private: CommandRunner* commandRunner; diff --git a/languages/cpp/src/BitwardenClient.cpp b/languages/cpp/src/BitwardenClient.cpp index ce161a2bf..2d11977d4 100644 --- a/languages/cpp/src/BitwardenClient.cpp +++ b/languages/cpp/src/BitwardenClient.cpp @@ -50,10 +50,11 @@ BitwardenClient::~BitwardenClient() { } } -void BitwardenClient::loginAccessToken(const std::string& accessToken) { +void BitwardenClient::loginAccessToken(const std::string& accessToken, const std::string& stateFile) { Command command; AccessTokenLoginRequest accessTokenLoginRequest; accessTokenLoginRequest.set_access_token(accessToken); + accessTokenLoginRequest.set_state_file(stateFile); command.set_login_access_token(accessTokenLoginRequest); auto deserializer = [](const char* response) -> ResponseForApiKeyLoginResponse { @@ -84,11 +85,11 @@ ProjectResponse BitwardenClient::createProject(const boost::uuids::uuid& organiz return projects.create(organizationId, name); } -ProjectResponse BitwardenClient::updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name){ +ProjectResponse BitwardenClient::updateProject(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& name){ if (!isClientOpen) { throw std::runtime_error("Client is not open."); } - return projects.update(id, organizationId, name); + return projects.update(organizationId, id, name); } ProjectsDeleteResponse BitwardenClient::deleteProjects(const std::vector& ids) { @@ -114,18 +115,25 @@ SecretResponse BitwardenClient::getSecret(const boost::uuids::uuid& id){ return secrets.get(id); } -SecretResponse BitwardenClient::createSecret(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds){ +SecretsResponse BitwardenClient::getSecretsByIds(const std::vector& ids){ if (!isClientOpen) { throw std::runtime_error("Client is not open."); } - return secrets.create(key, value, note, organizationId, projectIds); + return secrets.getByIds(ids); } -SecretResponse BitwardenClient::updateSecret(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds){ +SecretResponse BitwardenClient::createSecret(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds){ if (!isClientOpen) { throw std::runtime_error("Client is not open."); } - return secrets.update(id, key, value, note, organizationId, projectIds); + return secrets.create(organizationId, key, value, note, projectIds); +} + +SecretResponse BitwardenClient::updateSecret(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds){ + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.update(organizationId, id, key, value, note, projectIds); } SecretsDeleteResponse BitwardenClient::deleteSecrets(const std::vector& ids) { @@ -143,3 +151,10 @@ SecretIdentifiersResponse BitwardenClient::listSecrets(const boost::uuids::uuid return secrets.list(organizationId); } + +SecretsSyncResponse BitwardenClient::sync(const boost::uuids::uuid &organizationId, const std::chrono::system_clock::time_point &lastSyncedDate) { + if (!isClientOpen) { + throw std::runtime_error("Client is not open."); + } + return secrets.sync(organizationId, lastSyncedDate); +} diff --git a/languages/cpp/src/Projects.cpp b/languages/cpp/src/Projects.cpp index d0aa6ed49..b2fa1c688 100644 --- a/languages/cpp/src/Projects.cpp +++ b/languages/cpp/src/Projects.cpp @@ -67,7 +67,7 @@ ProjectResponse Projects::create(const boost::uuids::uuid& organizationId, const } } -ProjectResponse Projects::update(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name) { +ProjectResponse Projects::update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& name) { Command command; ProjectsCommand projectsCommand; ProjectPutRequest projectPutRequest; diff --git a/languages/cpp/src/Secrets.cpp b/languages/cpp/src/Secrets.cpp index e153ea7f1..1fff9441f 100644 --- a/languages/cpp/src/Secrets.cpp +++ b/languages/cpp/src/Secrets.cpp @@ -1,8 +1,8 @@ #include "Secrets.h" #include #include -#include -#include +#include +#include Secrets::Secrets(CommandRunner* commandRunner) : commandRunner(commandRunner) {} @@ -13,6 +13,13 @@ auto secretsDeserializer = [](const std::string& response) -> ResponseForSecretR return secretResponse; }; +auto secretsByIdsDeserializer = [](const std::string& response) -> ResponseForSecretsResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretsResponse secretsResponse; + Bitwarden::Sdk::from_json(jsonResponse, secretsResponse); + return secretsResponse; +}; + auto deleteSecretsDeserializer = [](const std::string& response) -> ResponseForSecretsDeleteResponse { nlohmann::json jsonResponse = nlohmann::json::parse(response); ResponseForSecretsDeleteResponse deleteSecretsResponse; @@ -27,6 +34,13 @@ auto secretListDeserializer = [](const std::string& response) -> ResponseForSecr return listResponse; }; +auto secretsSyncDeserializer = [](const std::string& response) -> ResponseForSecretsSyncResponse { + nlohmann::json jsonResponse = nlohmann::json::parse(response); + ResponseForSecretsSyncResponse syncResponse; + Bitwarden::Sdk::from_json(jsonResponse, syncResponse); + return syncResponse; +}; + SecretResponse Secrets::get(const boost::uuids::uuid& id) { Command command; SecretsCommand secretsCommand; @@ -46,7 +60,29 @@ SecretResponse Secrets::get(const boost::uuids::uuid& id) { } } -SecretResponse Secrets::create(const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds) { +SecretsResponse Secrets::getByIds(const std::vector& ids) { + Command command; + SecretsCommand secretsCommand; + SecretsGetRequest secretsGetRequest; + + std::vector idsStr; + for (const auto& id : ids) { + idsStr.push_back(boost::uuids::to_string(id)); + } + secretsGetRequest.set_ids(idsStr); + + secretsCommand.set_get_by_ids(secretsGetRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsByIdsDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in getSecretsByIds: " << ex.what() << std::endl; + throw ex; + } +} + +SecretResponse Secrets::create(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds) { Command command; SecretsCommand secretsCommand; SecretCreateRequest secretCreateRequest; @@ -75,7 +111,7 @@ SecretResponse Secrets::create(const std::string& key, const std::string& value, } } -SecretResponse Secrets::update(const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const boost::uuids::uuid& organizationId, const std::vector& projectIds) { +SecretResponse Secrets::update(const boost::uuids::uuid& organizationId, const boost::uuids::uuid& id, const std::string& key, const std::string& value, const std::string& note, const std::vector& projectIds) { Command command; SecretsCommand secretsCommand; SecretPutRequest secretPutRequest; @@ -147,3 +183,48 @@ SecretIdentifiersResponse Secrets::list(const boost::uuids::uuid& organizationId throw ex; } } + +SecretsSyncResponse Secrets::sync(const boost::uuids::uuid& organizationId, const boost::optional& lastSyncedDate) { + Command command; + SecretsCommand secretsCommand; + SecretsSyncRequest secretsSyncRequest; + + std::string orgIdStr = boost::uuids::to_string(organizationId); + secretsSyncRequest.set_organization_id(orgIdStr); + + if (lastSyncedDate.has_value()) { + auto timePoint = lastSyncedDate.value(); + + // Get time as time_t and milliseconds + auto timeT = std::chrono::system_clock::to_time_t(timePoint); + auto milliseconds = std::chrono::duration_cast(timePoint.time_since_epoch()) % 1000; + + // Convert to a tm struct + std::tm tm = *std::gmtime(&timeT); + + // Create a string stream to format the date and time + std::stringstream dateStream; + dateStream << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S"); + + // Add milliseconds + dateStream << '.' << std::setw(3) << std::setfill('0') << milliseconds.count() << 'Z'; + + // Convert to string + std::string dateStr = dateStream.str(); + + // Set the last synced date + secretsSyncRequest.set_last_synced_date(dateStr); + } else { + secretsSyncRequest.set_last_synced_date(boost::none); + } + + secretsCommand.set_sync(secretsSyncRequest); + command.set_secrets(secretsCommand); + + try { + return commandRunner->runCommand(command, secretsSyncDeserializer); + } catch (const std::exception& ex) { + std::cerr << "Error in syncSecrets: " << ex.what() << std::endl; + throw ex; + } +} diff --git a/languages/cpp/vcpkg.json b/languages/cpp/vcpkg.json index 06a7b968c..8e5b968b9 100644 --- a/languages/cpp/vcpkg.json +++ b/languages/cpp/vcpkg.json @@ -1,10 +1,11 @@ { - "name": "bitwarden-sdk-secrets", - "version": "0.1.0", - "homepage": "https://github.com/bitwarden/sdk/tree/languages/cpp", - "description": "Bitwarden Secrets Manager SDK for C++", - "dependencies": [ - "boost", - "nlohmann-json" - ] + "name": "bitwarden-sdk-secrets", + "version": "0.1.0", + "homepage": "https://github.com/bitwarden/sdk/tree/languages/cpp", + "description": "Bitwarden Secrets Manager SDK for C++", + "dependencies": [ + "boost-uuid", + "boost-optional", + "nlohmann-json" + ] }