Skip to content

Commit

Permalink
breaking: SM-1403 review and update cpp sdk (#1002)
Browse files Browse the repository at this point in the history
## 🎟️ Tracking

https://bitwarden.atlassian.net/browse/SM-1403

## 📔 Objective

Update the C++ bindings to include the features we support and make the
function parameters consistent with the other wrappers.

- `accessTokenLogin(token)` -> `loginAccessToken(token, stateFile)`
- `updateProject(projectId, orgId, name)` -> `updateProject(orgId,
projectId, name)`
- `createSecret(key, value, note, orgId, {projectId})` ->
`createSecret(orgId, key, value, note, {projectId})`
- `updateSecret(id, key, value, note, orgId, {projectId})` ->
`createSecret(orgId, id, key, value, note, {projectId})`
- add secret syncing
- add `getByIds`
- update the example wrapper to use the newer bindings as well as
produce output for each project/secret operation

## 📸 Screenshots

<!-- Required for any UI changes; delete if not applicable. Use fixed
width images for better display. -->

## ⏰ Reminders before review

- Contributor guidelines followed
- All formatters and local linters executed and passed
- Written new unit and / or integration tests where applicable
- Protected functional changes with optionality (feature flags)
- Used internationalization (i18n) for all UI strings
- CI builds passed
- Communicated to DevOps any deployment requirements
- Updated any necessary documentation (Confluence, contributing docs) or
informed the documentation
  team

## 🦮 Reviewer guidelines

<!-- Suggested interactions but feel free to use (or not) as you desire!
-->

- 👍 (`:+1:`) or similar for great changes
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
- ❓ (`:question:`) for questions
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry
that's not quite a confirmed
  issue and could potentially benefit from discussion
- 🎨 (`:art:`) for suggestions / improvements
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or
concerns needing attention
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or
indications of technical debt
- ⛏ (`:pick:`) for minor or nitpick changes

---------

Co-authored-by: Colton Hurst <[email protected]>
  • Loading branch information
tangowithfoxtrot and coltonhurst authored Sep 20, 2024
1 parent 3cb295c commit dd555ce
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 53 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 12 additions & 2 deletions languages/cpp/CMakeBuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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
Expand All @@ -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
Expand Down
33 changes: 24 additions & 9 deletions languages/cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ bitwardenSettings.set_identity_url("<bitwarden-identity>");

```c++
std::string accessToken = "<access-token>";
std::string stateFile = "<state-file>";
// Optional - argument in BitwardenClient
BitwardenClient bitwardenClient = BitwardenClient(bitwardenSettings);
bitwardenClient.accessTokenLogin(accessToken);
bitwardenClient.loginAccessToken(accessToken, stateFile);
```

### Create new project

```c++
boost::uuids::uuid organizationUuid = boost::uuids::string_generator()("<organization-id>");
ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "TestProject");
boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id());
```

### List all projects
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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<boost::uuids::uuid> 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
Expand Down
99 changes: 78 additions & 21 deletions languages/cpp/examples/Wrapper.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#include "BitwardenClient.h"
#include <boost/uuid/string_generator.hpp>
#include <cstdlib>
#include <chrono>

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;
Expand All @@ -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;
}
14 changes: 8 additions & 6 deletions languages/cpp/include/BitwardenClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<boost::uuids::uuid>& 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<boost::uuids::uuid>& 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<boost::uuids::uuid>& projectIds);
SecretsResponse getSecretsByIds(const std::vector<boost::uuids::uuid>& ids);
SecretResponse createSecret(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector<boost::uuids::uuid>& 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<boost::uuids::uuid>& projectIds);
SecretsDeleteResponse deleteSecrets(const std::vector<boost::uuids::uuid>& 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;
Expand Down
2 changes: 1 addition & 1 deletion languages/cpp/include/Projects.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<boost::uuids::uuid>& ids);
ProjectsResponse list(const boost::uuids::uuid& organizationId);

Expand Down
8 changes: 6 additions & 2 deletions languages/cpp/include/Secrets.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <vector>
#include <chrono>
#include <boost/optional.hpp>
#include <boost/uuid/uuid.hpp>
#include "CommandRunner.h"

Expand All @@ -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<boost::uuids::uuid>& 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<boost::uuids::uuid>& projectIds);
SecretsResponse getByIds(const std::vector<boost::uuids::uuid> &ids);
SecretResponse create(const boost::uuids::uuid& organizationId, const std::string& key, const std::string& value, const std::string& note, const std::vector<boost::uuids::uuid>& 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<boost::uuids::uuid>& projectIds);
SecretsDeleteResponse deleteSecrets(const std::vector<boost::uuids::uuid>& ids);
SecretIdentifiersResponse list(const boost::uuids::uuid& organizationId);
SecretsSyncResponse sync(const boost::uuids::uuid& organizationId, const boost::optional<std::chrono::system_clock::time_point>& lastSyncedDate);

private:
CommandRunner* commandRunner;
Expand Down
Loading

0 comments on commit dd555ce

Please sign in to comment.