Skip to content

Commit

Permalink
Cpp Wrapper for sdk (#259)
Browse files Browse the repository at this point in the history
## Type of change

<!-- (mark with an `X`) -->

```
- [ ] Bug fix
- [x ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
```

## Objective

Implemented C++ library that wraps native C library and exposed its
commands through BitwardenClient class.

## Code changes

- Implemented BitwardenLibrary for connection with `bitwarden_c` library
and its three functions: `init`, `run_command`, and `free_mem`
- Implemented CRUD operations for the Projects and Secrets for Bitwarden
Secrets Manager using the API
- Developed building mechanism that is building C++ dynamic library
using `Cmake`
- Added example in the `examples` directory
- Added documentation in .md files for CRUD operations use, building the
dynamic library, and for the client use

---------

Co-authored-by: Todosijevic-Slobodan <[email protected]>
Co-authored-by: Daniel García <[email protected]>
  • Loading branch information
3 people authored Nov 30, 2023
1 parent 9f77596 commit 0f5f11b
Show file tree
Hide file tree
Showing 18 changed files with 1,075 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/target
.DS_Store
.pytest_cache
.vscode/c_cpp_properties.json

# Build results
[Dd]ebug/
Expand Down Expand Up @@ -49,5 +50,6 @@ crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts
languages/csharp/Bitwarden.Sdk/schemas.cs
languages/js_webassembly/bitwarden_client/schemas.ts
languages/python/BitwardenClient/schemas.py
languages/cpp/include/schemas.hpp
languages/go/schema.go
languages/java/src/main/java/com/bitwarden/sdk/schema
33 changes: 33 additions & 0 deletions languages/cpp/CMakeBuild.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# CMAKE build

## INTRODUCTION

Cmake is used to build the c++ Bitwarden client library. Output should be placed in the build directory. The output contains two dynamic libraries: one that we are building `BitwardenClient` and another that the building library uses `bitwarden_c`.

## PREREQUISITES

- Cmake installed, minimum version 3.15
- `schemas.hpp` generated into `include` directory
- installed `nlohmann-json` library
- installed `boost` library

## BUILD commands

One should be in the root directory of the c++ wrapper (the same level where is CMakeLists.txt placed). Paths of the three libraries should be placed inside the cmake build command:

$ mkdir build
$ cd build
$ cmake .. -DNLOHMANN=/path/to/include/nlohmann -DBOOST=/path/to/include/boost -DTARGET=relative/path/to/libbitwarden_c
$ cmake --build .



## Example

macOS:

$ mkdir build
$ cd build
$ cmake .. -DNLOHMANN=/opt/hombrew/include -DBOOST=/opt/homebrew/include -DTARGET=../../target/release/libbitwarden_c.dylib
$ cmake --build .

36 changes: 36 additions & 0 deletions languages/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.15)
project(BitwardenClient)

set(CMAKE_CXX_STANDARD 20)

# Set placeholders to be passed from command line
set(NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER ${NLOHMANN})
set(BOOST_INCLUDE_DIR_PLACEHOLDER ${BOOST})
set(TARGET_INCLUDE_DIR_PLACEHOLDER ${TARGET})

# Specify the locations of nlohmann.json and Boost libraries
find_path(NLOHMANN_JSON_INCLUDE_DIR nlohmann/json.hpp HINTS ${NLOHMANN_JSON_INCLUDE_DIR_PLACEHOLDER})
find_path(BOOST_INCLUDE_DIR boost/optional.hpp HINTS ${BOOST_INCLUDE_DIR_PLACEHOLDER})

# Include directories for library
include_directories(include ${NLOHMANN_JSON_INCLUDE_DIR} ${BOOST_INCLUDE_DIR})

# Add library source files
file(GLOB SOURCES "src/*.cpp")

# Add library source files along with the schemas.cpp file
add_library(BitwardenClient SHARED ${SOURCES} ${SCHEMAS_SOURCE})

# Set path for native library loading
set(LIB_BITWARDEN_C "${CMAKE_SOURCE_DIR}/${TARGET}")

# Copy the library to the build directory before building
add_custom_command(
TARGET BitwardenClient PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${LIB_BITWARDEN_C}
$<TARGET_FILE_DIR:BitwardenClient>
)

# Link libraries
target_link_libraries(BitwardenClient PRIVATE ${LIB_BITWARDEN_C})
70 changes: 70 additions & 0 deletions languages/cpp/ExampleUse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# EXAMPLES


## PREREQUISITES

### BITWARDEN Libraries
One should have two libraries at the same path:
- `BitwardeClient`
- `bitwarden_c`

It should look like `libBitwardeClient.dylib` and `libbitwarden_c.dylib` for the macOS.

For Linux: `libBitwardeClient.so` and `libbitwarden_c.so`
For Windows: `BitwardeClient.dll` and `bitwarden_c.dll`

### INCLUDE directory

`include` directory contains:
- `BitwardenLibrary.h`
- `BitwardenClient.h`
- `BitwardenSettings.h`
- `CommandRunner.h`
- `Projects.h`
- `Secrets.h`
- `schemas.hpp`

### Other libraries
- `nlohmann-json` (https://github.com/nlohmann/json)
- `boost` (https://www.boost.org/)


### COMPILING

One could use g++/clang++ for compiling.
Example of the folder structure (macOS):

--root
--build
`libBitwardenClient.dylib`
`libbitwarden_c.dylib`
--include
--`BitwardenLibrary.h`
--`BitwardenClient.h`
--`BitwardenSettings.h`
--`CommandRunner.h`
--`Projects.h`
--`Secrets.h`
--`schemas.hpp`
--examples
--`Wrapper.cpp`


1. $ export ACCESS_TOKEN=<"access-token">
2. $ export ORGANIZATION_ID=<"organization-id">
3. $ export DYLD_LIBRARY_PATH=/path/to/your/library:$DYLD_LIBRARY_PATH

The last step is neccessary to add the path for the dynamic library (macOS).
For the Linux one should use:
$ export LD_LIBRARY_PATH=/path/to/your/library:$LD_LIBRARY_PATH
For the Windows:
$ set PATH=%PATH%;C:\path\to\your\library

4. $ cd examples
5. $ clang++ -std=c++20 -I../include -I/path/to/include/nlohmann -I/path/to/include/boost -L../build/ -o MyBitwardenApp Wrapper.cpp -lBitwardenClient -ldl

for Windows `-ldl` should be excluded,

The result is `MyBitwardenApp` in the `examples` directory, and one can run it from the `examples` directory:

6. $ ./MyBitwardenApp
97 changes: 97 additions & 0 deletions languages/cpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Bitwarden Secrets Manager SDK

C++ bindings for interacting with the [Bitwarden Secrets Manager]. This is a beta release and might be missing some functionality.

## Create access token

Review the help documentation on [Access Tokens]

## Usage code snippets

### Client settings

```c++
// Optional - if not stressed, then default values are used
BitwardenSettings bitwardenSettings;
bitwardenSettings.set_api_url("<bitwarden-url>");
bitwardenSettings.set_identity_url("<bitwarden-identity>");
```


### Create new Bitwarden client

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

### Create new project

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

### List all projects

```c++
ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid);
```

### Get project details

```c++
boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id());
ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId);
```

### Update project

```c++
boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id());
ProjectResponse projectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "TestProjectUpdated");
```

### Delete projects

```c++
SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId});
```
### Add new secret
```c++
std::string key = "key";
std::string value = "value";
std::string note = "note";
SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId});
```

### List secrets

```c++
SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid);
```

### Get secret details

```
boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id());
SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId);
```

### Update secret
```c++
SecretResponse secretResponseUpdate = bitwardenClient.updateSecret(secretId, "key2", "value2", "note2", organizationUuid, {projectId});
```
# Delete secrets
```c++
SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId});
```

[Access Tokens]: https://bitwarden.com/help/access-tokens/
[Bitwarden Secrets Manager]: https://bitwarden.com/products/secrets-manager/
73 changes: 73 additions & 0 deletions languages/cpp/examples/Wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "BitwardenClient.h"
#include <boost/uuid/string_generator.hpp>
#include <cstdlib>

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");

if (!accessTokenEnv || !organizationIdEnv) {
std::cerr << "Error: Environment variables ACCESS_TOKEN or ORGANIZATION_ID not set." << std::endl;
return 1;
}

std::string accessToken = accessTokenEnv;
std::string organizationId = organizationIdEnv;



// Optional - commented to use default values
// BitwardenSettings bitwardenSettings;
// bitwardenSettings.set_api_url("<bitwarden-url>");
// bitwardenSettings.set_identity_url("<bitwarden-identity>");

// Create a Bitwarden client instance
BitwardenClient bitwardenClient = BitwardenClient();
// // Access token login
bitwardenClient.accessTokenLogin(accessToken);
// Organization ID
boost::uuids::uuid organizationUuid = boost::uuids::string_generator()(organizationId);

// // Create a new project
ProjectResponse projectResponseCreate = bitwardenClient.createProject(organizationUuid, "NewTestProject");
boost::uuids::uuid projectId = boost::uuids::string_generator()(projectResponseCreate.get_id());

// List projects
ProjectsResponse projectResponseList = bitwardenClient.listProjects(organizationUuid);

// Get project details
ProjectResponse projectResponseGet = bitwardenClient.getProject(projectId);

// Update project
ProjectResponse ProjectResponseUpdate = bitwardenClient.updateProject(projectId, organizationUuid, "NewTestProject2");

// Secrets
std::string key = "key";
std::string value = "value";
std::string note = "note";

// Create a new secret
SecretResponse secretResponseCreate = bitwardenClient.createSecret(key, value, note, organizationUuid, {projectId});
boost::uuids::uuid secretId = boost::uuids::string_generator()(secretResponseCreate.get_id());

// List secrets
SecretIdentifiersResponse secretIdentifiersResponse = bitwardenClient.listSecrets(organizationUuid);

// Get secret details
SecretResponse secretResponseGet = bitwardenClient.getSecret(secretId);

// Update secret
key = "key2";
value = "value2";
note = "note2";
SecretResponse responseForSecretResponseUpdate = bitwardenClient.updateSecret(secretId, key, value, note, organizationUuid, {projectId});

// Delete secrets
SecretsDeleteResponse secretsDeleteResponse = bitwardenClient.deleteSecrets({secretId});

// Delete projects
ProjectsDeleteResponse projectsDeleteResponse = bitwardenClient.deleteProjects({projectId});

return 0;
}
36 changes: 36 additions & 0 deletions languages/cpp/include/BitwardenClient.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include "CommandRunner.h"
#include "BitwardenSettings.h"
#include "Projects.h"
#include "Secrets.h"
#include <functional>
#include <string>

class BitwardenClient {
public:
BitwardenClient(const BitwardenSettings& bitwardenSettings = BitwardenSettings());
~BitwardenClient();

void accessTokenLogin(const std::string& accessToken);
ProjectResponse getProject(const boost::uuids::uuid& id);
ProjectResponse createProject(const boost::uuids::uuid& organizationId, const std::string& name);
ProjectResponse updateProject(const boost::uuids::uuid& id, const boost::uuids::uuid& organizationId, const std::string& name);
ProjectsDeleteResponse deleteProjects(const std::vector<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);
SecretsDeleteResponse deleteSecrets(const std::vector<boost::uuids::uuid>& ids);
SecretIdentifiersResponse listSecrets(const boost::uuids::uuid& organizationId);

private:
BitwardenLibrary* library;
void* client;
CommandRunner* commandRunner;
Projects projects;
Secrets secrets;
bool isClientOpen;
ClientSettings clientSettings;

};
27 changes: 27 additions & 0 deletions languages/cpp/include/BitwardenLibrary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <string>

#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif

class BitwardenLibrary {
public:
BitwardenLibrary(const std::string& providedLibraryPath);
~BitwardenLibrary();

void* init(const char* clientSettingsJson);
void free_mem(void* client);
const char* run_command(const char* commandJson, void* client);

private:
#ifdef _WIN32
HMODULE libraryHandle;
#else
void* libraryHandle;
#endif
};

Loading

0 comments on commit 0f5f11b

Please sign in to comment.