From 520a958e9ba62c7ec39ee970862570c456e3375c Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Fri, 23 Aug 2024 12:34:57 -0400
Subject: [PATCH 01/24] Add `run_command_async`
---
crates/bitwarden-c/src/c.rs | 26 ++++++++++
.../Bitwarden.Sdk.Samples.csproj | 2 +-
.../csharp/Bitwarden.Sdk/BitwardenClient.cs | 6 +--
.../csharp/Bitwarden.Sdk/BitwardenLibrary.cs | 50 ++++++++++++++++---
.../csharp/Bitwarden.Sdk/CommandRunner.cs | 7 +++
5 files changed, 80 insertions(+), 11 deletions(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index 158c38025..2d10e44c8 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -28,6 +28,32 @@ pub extern "C" fn run_command(c_str_ptr: *const c_char, client_ptr: *const CClie
}
}
+type OnCompletedCallback = unsafe extern "C" fn(result: *mut c_char) -> ();
+
+#[no_mangle]
+pub extern "C" fn run_command_async(c_str_ptr: *const c_char, client_ptr: *const CClient, on_completed_callback: OnCompletedCallback) -> () {
+ let client = unsafe { ffi_ref!(client_ptr) };
+ let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr) }.to_bytes())
+ .expect("Input should be a valid string")
+ // Languages may assume that the string is collectable as soon as this method exits
+ // but it's not since the request will be run in the background
+ // so we need to make our own copy.
+ .to_owned();
+
+ client
+ .runtime
+ .spawn(async move {
+ let result = client.client.run_command(input_str.as_str()).await;
+ let str_result = match std::ffi::CString::new(result) {
+ Ok(cstr) => cstr.into_raw(),
+ Err(_) => panic!("failed to return comment result: null encountered"),
+ };
+
+ // run completed function
+ unsafe { on_completed_callback(str_result) }
+ });
+}
+
// Init client, potential leak! You need to call free_mem after this!
#[no_mangle]
pub extern "C" fn init(c_str_ptr: *const c_char) -> *mut CClient {
diff --git a/languages/csharp/Bitwarden.Sdk.Samples/Bitwarden.Sdk.Samples.csproj b/languages/csharp/Bitwarden.Sdk.Samples/Bitwarden.Sdk.Samples.csproj
index 5b189d8ca..ab616ab83 100644
--- a/languages/csharp/Bitwarden.Sdk.Samples/Bitwarden.Sdk.Samples.csproj
+++ b/languages/csharp/Bitwarden.Sdk.Samples/Bitwarden.Sdk.Samples.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
enable
enable
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs b/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs
index cb352d84f..3c8f62284 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs
@@ -20,10 +20,10 @@ public BitwardenClient(BitwardenSettings? settings = null)
Secrets = new SecretsClient(_commandRunner);
}
- public void AccessTokenLogin(string accessToken)
+ public async Task AccessTokenLoginAsync(string accessToken)
{
- var command = new Command { AccessTokenLogin = new AccessTokenLoginRequest { AccessToken = accessToken } };
- var response = _commandRunner.RunCommand(command);
+ var command = new Command { LoginAccessToken = new AccessTokenLoginRequest { AccessToken = accessToken } };
+ var response = await _commandRunner.RunCommandAsync(command);
if (response is not { Success: true })
{
throw new BitwardenAuthException(response != null ? response.ErrorMessage : "Login failed");
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
index ada399401..34daac570 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
@@ -2,20 +2,56 @@
namespace Bitwarden.Sdk;
-internal static class BitwardenLibrary
+internal static partial class BitwardenLibrary
{
- [DllImport("bitwarden_c", CallingConvention = CallingConvention.Cdecl)]
- private static extern BitwardenSafeHandle init(string settings);
+ [LibraryImport("bitwarden_c", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial BitwardenSafeHandle init(string settings);
- [DllImport("bitwarden_c", CallingConvention = CallingConvention.Cdecl)]
- private static extern void free_mem(IntPtr handle);
+ [LibraryImport("bitwarden_c", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void free_mem(IntPtr handle);
- [DllImport("bitwarden_c", CallingConvention = CallingConvention.Cdecl)]
- private static extern string run_command(string json, BitwardenSafeHandle handle);
+ [LibraryImport("bitwarden_c", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial string run_command(string json, BitwardenSafeHandle handle);
+
+ internal delegate void OnCompleteCallback(IntPtr json);
+
+ [LibraryImport("bitwarden_c", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void run_command_async(string json, BitwardenSafeHandle handle, OnCompleteCallback on_completed_callback);
internal static BitwardenSafeHandle Init(string settings) => init(settings);
internal static void FreeMemory(IntPtr handle) => free_mem(handle);
internal static string RunCommand(string json, BitwardenSafeHandle handle) => run_command(json, handle);
+
+ internal static Task RunCommandAsync(string json, BitwardenSafeHandle handle, CancellationToken token = default)
+ {
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ try
+ {
+ run_command_async(json, handle, (pointer) =>
+ {
+ try
+ {
+ var stringResult = Marshal.PtrToStringUTF8(pointer);
+ Console.WriteLine($"Setting Result {stringResult}");
+ tcs.SetResult(stringResult);
+ Console.WriteLine("Set Result");
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem(pointer);
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+
+ // TODO: Register cancellation on token
+
+ return tcs.Task;
+ }
}
diff --git a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
index fbd6b7e31..a710b737e 100644
--- a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
+++ b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
@@ -17,4 +17,11 @@ internal CommandRunner(BitwardenSafeHandle handle)
var result = BitwardenLibrary.RunCommand(req, _handle);
return JsonSerializer.Deserialize(result, Converter.Settings);
}
+
+ internal async Task RunCommandAsync(Command command)
+ {
+ var req = JsonSerializer.Serialize(command, Converter.Settings);
+ var result = await BitwardenLibrary.RunCommandAsync(req, _handle);
+ return JsonSerializer.Deserialize(result, Converter.Settings);
+ }
}
From a91318973fe2163b3929468f337e77a210f00d70 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Fri, 23 Aug 2024 12:53:39 -0400
Subject: [PATCH 02/24] Remove Logs
---
languages/csharp/Bitwarden.Sdk.Samples/Program.cs | 4 ++--
languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj | 6 +++---
languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs | 8 +++-----
3 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/languages/csharp/Bitwarden.Sdk.Samples/Program.cs b/languages/csharp/Bitwarden.Sdk.Samples/Program.cs
index ee6834979..670bd7de2 100644
--- a/languages/csharp/Bitwarden.Sdk.Samples/Program.cs
+++ b/languages/csharp/Bitwarden.Sdk.Samples/Program.cs
@@ -1,4 +1,4 @@
-using Bitwarden.Sdk;
+using Bitwarden.Sdk;
// Configure secrets
var accessToken = Environment.GetEnvironmentVariable("ACCESS_TOKEN")!;
@@ -9,7 +9,7 @@
using var bitwardenClient = new BitwardenClient();
// Authenticate
-bitwardenClient.AccessTokenLogin(accessToken);
+await bitwardenClient.AccessTokenLoginAsync(accessToken);
// Project operations
var projectResponse = bitwardenClient.Projects.Create(organizationId, "NewTestProject");
diff --git a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj
index d2363f4a9..2d786a184 100644
--- a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj
+++ b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj
@@ -1,11 +1,11 @@
- net6.0
+ net8.0
enable
enable
Bitwarden.Sdk
-
+ true
Bitwarden Secrets Manager SDK
Bitwarden Inc.
.NET bindings for interacting with the Bitwarden Secrets Manager
@@ -74,4 +74,4 @@
runtimes/win-x64/native
-
\ No newline at end of file
+
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
index 34daac570..fa7f4b267 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
@@ -30,18 +30,16 @@ internal static Task RunCommandAsync(string json, BitwardenSafeHandle ha
try
{
- run_command_async(json, handle, (pointer) =>
+ run_command_async(json, handle, (resultPointer) =>
{
try
{
- var stringResult = Marshal.PtrToStringUTF8(pointer);
- Console.WriteLine($"Setting Result {stringResult}");
+ var stringResult = Marshal.PtrToStringUTF8(resultPointer);
tcs.SetResult(stringResult);
- Console.WriteLine("Set Result");
}
finally
{
- Marshal.FreeCoTaskMem(pointer);
+ Marshal.FreeCoTaskMem(resultPointer);
}
});
}
From 497979e562a74c03ac855c4b1a5caa55f47f9503 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Fri, 23 Aug 2024 13:09:14 -0400
Subject: [PATCH 03/24] Formatting
---
crates/bitwarden-c/src/c.rs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index 2d10e44c8..3ce2add63 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -31,7 +31,11 @@ pub extern "C" fn run_command(c_str_ptr: *const c_char, client_ptr: *const CClie
type OnCompletedCallback = unsafe extern "C" fn(result: *mut c_char) -> ();
#[no_mangle]
-pub extern "C" fn run_command_async(c_str_ptr: *const c_char, client_ptr: *const CClient, on_completed_callback: OnCompletedCallback) -> () {
+pub extern "C" fn run_command_async(
+ c_str_ptr: *const c_char,
+ client_ptr: *const CClient,
+ on_completed_callback: OnCompletedCallback
+) -> () {
let client = unsafe { ffi_ref!(client_ptr) };
let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr) }.to_bytes())
.expect("Input should be a valid string")
From c5c8d175611802ec86d65ad81886467dab09a609 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Fri, 23 Aug 2024 13:15:20 -0400
Subject: [PATCH 04/24] Formatting
---
crates/bitwarden-c/src/c.rs | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index 3ce2add63..e0f185f04 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -34,7 +34,7 @@ type OnCompletedCallback = unsafe extern "C" fn(result: *mut c_char) -> ();
pub extern "C" fn run_command_async(
c_str_ptr: *const c_char,
client_ptr: *const CClient,
- on_completed_callback: OnCompletedCallback
+ on_completed_callback: OnCompletedCallback,
) -> () {
let client = unsafe { ffi_ref!(client_ptr) };
let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr) }.to_bytes())
@@ -44,18 +44,16 @@ pub extern "C" fn run_command_async(
// so we need to make our own copy.
.to_owned();
- client
- .runtime
- .spawn(async move {
- let result = client.client.run_command(input_str.as_str()).await;
- let str_result = match std::ffi::CString::new(result) {
- Ok(cstr) => cstr.into_raw(),
- Err(_) => panic!("failed to return comment result: null encountered"),
- };
+ client.runtime.spawn(async move {
+ let result = client.client.run_command(input_str.as_str()).await;
+ let str_result = match std::ffi::CString::new(result) {
+ Ok(cstr) => cstr.into_raw(),
+ Err(_) => panic!("failed to return comment result: null encountered"),
+ };
- // run completed function
- unsafe { on_completed_callback(str_result) }
- });
+ // run completed function
+ unsafe { on_completed_callback(str_result) }
+ });
}
// Init client, potential leak! You need to call free_mem after this!
From 2d2801b1e17c8d5456558481a987566a11bc9900 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Fri, 23 Aug 2024 16:41:26 -0400
Subject: [PATCH 05/24] Free String On Rust Side
---
crates/bitwarden-c/src/c.rs | 7 +++++--
languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs | 11 ++---------
2 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index e0f185f04..3eba7c9ab 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -1,4 +1,4 @@
-use std::{ffi::CStr, os::raw::c_char, str};
+use std::{ffi::{CStr, CString}, os::raw::c_char, str};
use bitwarden_json::client::Client;
@@ -52,7 +52,10 @@ pub extern "C" fn run_command_async(
};
// run completed function
- unsafe { on_completed_callback(str_result) }
+ unsafe {
+ on_completed_callback(str_result);
+ let _ = CString::from_raw(str_result);
+ }
});
}
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
index fa7f4b267..eb059ff2d 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
@@ -32,15 +32,8 @@ internal static Task RunCommandAsync(string json, BitwardenSafeHandle ha
{
run_command_async(json, handle, (resultPointer) =>
{
- try
- {
- var stringResult = Marshal.PtrToStringUTF8(resultPointer);
- tcs.SetResult(stringResult);
- }
- finally
- {
- Marshal.FreeCoTaskMem(resultPointer);
- }
+ var stringResult = Marshal.PtrToStringUTF8(resultPointer);
+ tcs.SetResult(stringResult);
});
}
catch (Exception ex)
From adb445fb9e599b5eec8e397591f293e0cd4ec18d Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Sun, 25 Aug 2024 16:21:29 -0400
Subject: [PATCH 06/24] Formatting
---
crates/bitwarden-c/src/c.rs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index 3eba7c9ab..7cbb7a473 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -1,4 +1,8 @@
-use std::{ffi::{CStr, CString}, os::raw::c_char, str};
+use std::{
+ ffi::{CStr, CString},
+ os::raw::c_char,
+ str
+};
use bitwarden_json::client::Client;
From a58121a5ec87b783db7fd21b7be11a9e932c1e17 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 3 Sep 2024 20:05:17 -0400
Subject: [PATCH 07/24] Migrate All C# Clients to Async
---
.../csharp/Bitwarden.Sdk.Samples/Program.cs | 26 ++++++++---------
languages/csharp/Bitwarden.Sdk/AuthClient.cs | 4 +--
.../csharp/Bitwarden.Sdk/CommandRunner.cs | 4 +--
.../csharp/Bitwarden.Sdk/ProjectsClient.cs | 20 ++++++-------
.../csharp/Bitwarden.Sdk/SecretsClient.cs | 28 +++++++++----------
5 files changed, 41 insertions(+), 41 deletions(-)
diff --git a/languages/csharp/Bitwarden.Sdk.Samples/Program.cs b/languages/csharp/Bitwarden.Sdk.Samples/Program.cs
index 267b02f52..64837ee76 100644
--- a/languages/csharp/Bitwarden.Sdk.Samples/Program.cs
+++ b/languages/csharp/Bitwarden.Sdk.Samples/Program.cs
@@ -15,10 +15,10 @@
});
// Authenticate
-bitwardenClient.Auth.LoginAccessToken(accessToken, stateFile);
+await bitwardenClient.Auth.LoginAccessTokenAsync(accessToken, stateFile);
// Projects List
-var projectsList = bitwardenClient.Projects.List(organizationId).Data;
+var projectsList = (await bitwardenClient.Projects.ListAsync(organizationId)).Data;
Console.WriteLine("A list of all projects:");
foreach (ProjectResponse pr in projectsList)
{
@@ -30,9 +30,9 @@
// Projects Create, Update, & Get
Console.WriteLine("Creating and updating a project");
-var projectResponse = bitwardenClient.Projects.Create(organizationId, "NewTestProject");
-projectResponse = bitwardenClient.Projects.Update(organizationId, projectResponse.Id, "NewTestProject Renamed");
-projectResponse = bitwardenClient.Projects.Get(projectResponse.Id);
+var projectResponse = await bitwardenClient.Projects.CreateAsync(organizationId, "NewTestProject");
+projectResponse = await bitwardenClient.Projects.UpdateAsync(organizationId, projectResponse.Id, "NewTestProject Renamed");
+projectResponse = await bitwardenClient.Projects.GetAsync(projectResponse.Id);
Console.WriteLine("Here is the project we created and updated:");
Console.WriteLine(projectResponse.Name);
@@ -40,7 +40,7 @@
Console.ReadLine();
// Secrets list
-var secretsList = bitwardenClient.Secrets.List(organizationId).Data;
+var secretsList = (await bitwardenClient.Secrets.ListAsync(organizationId)).Data;
Console.WriteLine("A list of all secrets:");
foreach (SecretIdentifierResponse sr in secretsList)
{
@@ -52,9 +52,9 @@
// Secrets Create, Update, Get
Console.WriteLine("Creating and updating a secret");
-var secretResponse = bitwardenClient.Secrets.Create(organizationId, "New Secret", "the secret value", "the secret note", new[] { projectResponse.Id });
-secretResponse = bitwardenClient.Secrets.Update(organizationId, secretResponse.Id, "New Secret Name", "the secret value", "the secret note", new[] { projectResponse.Id });
-secretResponse = bitwardenClient.Secrets.Get(secretResponse.Id);
+var secretResponse = await bitwardenClient.Secrets.CreateAsync(organizationId, "New Secret", "the secret value", "the secret note", new[] { projectResponse.Id });
+secretResponse = await bitwardenClient.Secrets.UpdateAsync(organizationId, secretResponse.Id, "New Secret Name", "the secret value", "the secret note", new[] { projectResponse.Id });
+secretResponse = await bitwardenClient.Secrets.GetAsync(secretResponse.Id);
Console.WriteLine("Here is the secret we created and updated:");
Console.WriteLine(secretResponse.Key);
@@ -62,12 +62,12 @@
Console.ReadLine();
// Secrets GetByIds
-var secretsResponse = bitwardenClient.Secrets.GetByIds(new[] { secretResponse.Id });
+var secretsResponse = await bitwardenClient.Secrets.GetByIdsAsync(new[] { secretResponse.Id });
// Secrets Sync
-var syncResponse = bitwardenClient.Secrets.Sync(organizationId, null);
+var syncResponse = await bitwardenClient.Secrets.SyncAsync(organizationId, null);
// Secrets & Projects Delete
Console.WriteLine("Deleting our secret and project");
-bitwardenClient.Secrets.Delete(new[] { secretResponse.Id });
-bitwardenClient.Projects.Delete(new[] { projectResponse.Id });
+await bitwardenClient.Secrets.DeleteAsync(new[] { secretResponse.Id });
+await bitwardenClient.Projects.DeleteAsync(new[] { projectResponse.Id });
diff --git a/languages/csharp/Bitwarden.Sdk/AuthClient.cs b/languages/csharp/Bitwarden.Sdk/AuthClient.cs
index e801f2aee..3e148143a 100644
--- a/languages/csharp/Bitwarden.Sdk/AuthClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/AuthClient.cs
@@ -9,10 +9,10 @@ internal AuthClient(CommandRunner commandRunner)
_commandRunner = commandRunner;
}
- public void LoginAccessToken(string accessToken, string stateFile = "")
+ public async Task LoginAccessTokenAsync(string accessToken, string stateFile = "")
{
var command = new Command { LoginAccessToken = new AccessTokenLoginRequest { AccessToken = accessToken, StateFile = stateFile } };
- var response = _commandRunner.RunCommand(command);
+ var response = await _commandRunner.RunCommandAsync(command);
if (response is not { Success: true })
{
throw new BitwardenAuthException(response != null ? response.ErrorMessage : "Login failed");
diff --git a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
index a710b737e..41ecc3734 100644
--- a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
+++ b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
@@ -18,10 +18,10 @@ internal CommandRunner(BitwardenSafeHandle handle)
return JsonSerializer.Deserialize(result, Converter.Settings);
}
- internal async Task RunCommandAsync(Command command)
+ internal async Task RunCommandAsync(Command command, CancellationToken cancellationToken = default)
{
var req = JsonSerializer.Serialize(command, Converter.Settings);
- var result = await BitwardenLibrary.RunCommandAsync(req, _handle);
+ var result = await BitwardenLibrary.RunCommandAsync(req, _handle, cancellationToken);
return JsonSerializer.Deserialize(result, Converter.Settings);
}
}
diff --git a/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs b/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs
index 47a419364..e612b2e14 100644
--- a/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs
@@ -9,10 +9,10 @@ internal ProjectsClient(CommandRunner commandRunner)
_commandRunner = commandRunner;
}
- public ProjectResponse Get(Guid id)
+ public async Task GetAsync(Guid id)
{
var command = new Command { Projects = new ProjectsCommand { Get = new ProjectGetRequest { Id = id } } };
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -22,7 +22,7 @@ public ProjectResponse Get(Guid id)
throw new BitwardenException(result != null ? result.ErrorMessage : "Project not found");
}
- public ProjectResponse Create(Guid organizationId, string name)
+ public async Task CreateAsync(Guid organizationId, string name)
{
var command = new Command
{
@@ -31,7 +31,7 @@ public ProjectResponse Create(Guid organizationId, string name)
Create = new ProjectCreateRequest { OrganizationId = organizationId, Name = name }
}
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -41,7 +41,7 @@ public ProjectResponse Create(Guid organizationId, string name)
throw new BitwardenException(result != null ? result.ErrorMessage : "Project create failed");
}
- public ProjectResponse Update(Guid organizationId, Guid id, string name)
+ public async Task UpdateAsync(Guid organizationId, Guid id, string name)
{
var command = new Command
{
@@ -50,7 +50,7 @@ public ProjectResponse Update(Guid organizationId, Guid id, string name)
Update = new ProjectPutRequest { Id = id, OrganizationId = organizationId, Name = name }
}
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -60,13 +60,13 @@ public ProjectResponse Update(Guid organizationId, Guid id, string name)
throw new BitwardenException(result != null ? result.ErrorMessage : "Project update failed");
}
- public ProjectsDeleteResponse Delete(Guid[] ids)
+ public async Task DeleteAsync(Guid[] ids)
{
var command = new Command
{
Projects = new ProjectsCommand { Delete = new ProjectsDeleteRequest { Ids = ids } }
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -76,13 +76,13 @@ public ProjectsDeleteResponse Delete(Guid[] ids)
throw new BitwardenException(result != null ? result.ErrorMessage : "Project delete failed");
}
- public ProjectsResponse List(Guid organizationId)
+ public async Task ListAsync(Guid organizationId)
{
var command = new Command
{
Projects = new ProjectsCommand { List = new ProjectsListRequest { OrganizationId = organizationId } }
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
diff --git a/languages/csharp/Bitwarden.Sdk/SecretsClient.cs b/languages/csharp/Bitwarden.Sdk/SecretsClient.cs
index 5dd77fc6b..743c92a81 100644
--- a/languages/csharp/Bitwarden.Sdk/SecretsClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/SecretsClient.cs
@@ -9,10 +9,10 @@ internal SecretsClient(CommandRunner commandRunner)
_commandRunner = commandRunner;
}
- public SecretResponse Get(Guid id)
+ public async Task GetAsync(Guid id)
{
var command = new Command { Secrets = new SecretsCommand { Get = new SecretGetRequest { Id = id } } };
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -22,10 +22,10 @@ public SecretResponse Get(Guid id)
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret not found");
}
- public SecretsResponse GetByIds(Guid[] ids)
+ public async Task GetByIdsAsync(Guid[] ids)
{
var command = new Command { Secrets = new SecretsCommand { GetByIds = new SecretsGetRequest { Ids = ids } } };
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -35,7 +35,7 @@ public SecretsResponse GetByIds(Guid[] ids)
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret not found");
}
- public SecretResponse Create(Guid organizationId, string key, string value, string note, Guid[] projectIds)
+ public async Task CreateAsync(Guid organizationId, string key, string value, string note, Guid[] projectIds)
{
var command = new Command
{
@@ -52,7 +52,7 @@ public SecretResponse Create(Guid organizationId, string key, string value, stri
}
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -62,7 +62,7 @@ public SecretResponse Create(Guid organizationId, string key, string value, stri
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret create failed");
}
- public SecretResponse Update(Guid organizationId, Guid id, string key, string value, string note, Guid[] projectIds)
+ public async Task UpdateAsync(Guid organizationId, Guid id, string key, string value, string note, Guid[] projectIds)
{
var command = new Command
{
@@ -80,7 +80,7 @@ public SecretResponse Update(Guid organizationId, Guid id, string key, string va
}
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -90,10 +90,10 @@ public SecretResponse Update(Guid organizationId, Guid id, string key, string va
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret update failed");
}
- public SecretsDeleteResponse Delete(Guid[] ids)
+ public async Task DeleteAsync(Guid[] ids)
{
var command = new Command { Secrets = new SecretsCommand { Delete = new SecretsDeleteRequest { Ids = ids } } };
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -103,13 +103,13 @@ public SecretsDeleteResponse Delete(Guid[] ids)
throw new BitwardenException(result != null ? result.ErrorMessage : "Secrets delete failed");
}
- public SecretIdentifiersResponse List(Guid organizationId)
+ public async Task ListAsync(Guid organizationId)
{
var command = new Command
{
Secrets = new SecretsCommand { List = new SecretIdentifiersRequest { OrganizationId = organizationId } }
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
@@ -119,7 +119,7 @@ public SecretIdentifiersResponse List(Guid organizationId)
throw new BitwardenException(result != null ? result.ErrorMessage : "No secrets for given organization");
}
- public SecretsSyncResponse Sync(Guid organizationId, DateTimeOffset? lastSyncedDate)
+ public async Task SyncAsync(Guid organizationId, DateTimeOffset? lastSyncedDate)
{
var command = new Command
{
@@ -133,7 +133,7 @@ public SecretsSyncResponse Sync(Guid organizationId, DateTimeOffset? lastSyncedD
}
};
- var result = _commandRunner.RunCommand(command);
+ var result = await _commandRunner.RunCommandAsync(command);
if (result is { Success: true })
{
From bb1bc1e949c2440f7d3d3c8a42dcd1aafe81ba6a Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 16:30:49 -0400
Subject: [PATCH 08/24] Support Cancellation
---
crates/bitwarden-c/src/c.rs | 30 +++++++++-
crates/bitwarden-core/Cargo.toml | 2 +-
.../src/platform/client_platform.rs | 20 +++++++
crates/bitwarden-json/src/client.rs | 11 ++++
crates/bitwarden-json/src/command.rs | 10 ++++
.../Bitwarden.Sdk.Tests.csproj | 29 ++++++++++
.../Bitwarden.Sdk.Tests/GlobalUsings.cs | 1 +
.../Bitwarden.Sdk.Tests/InteropTests.cs | 35 +++++++++++
.../csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj | 2 +
.../Bitwarden.Sdk/BitwardenClient.Debug.cs | 58 +++++++++++++++++++
.../csharp/Bitwarden.Sdk/BitwardenClient.cs | 6 +-
.../csharp/Bitwarden.Sdk/BitwardenLibrary.cs | 31 ++++++++--
.../csharp/Bitwarden.Sdk/CommandRunner.cs | 6 ++
languages/csharp/Bitwarden.sln | 7 +++
14 files changed, 239 insertions(+), 9 deletions(-)
create mode 100644 languages/csharp/Bitwarden.Sdk.Tests/Bitwarden.Sdk.Tests.csproj
create mode 100644 languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
create mode 100644 languages/csharp/Bitwarden.Sdk.Tests/InteropTests.cs
create mode 100644 languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index 7cbb7a473..f1cb1c6f3 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -5,6 +5,7 @@ use std::{
};
use bitwarden_json::client::Client;
+use tokio::task::JoinHandle;
use crate::{box_ptr, ffi_ref};
@@ -39,7 +40,9 @@ pub extern "C" fn run_command_async(
c_str_ptr: *const c_char,
client_ptr: *const CClient,
on_completed_callback: OnCompletedCallback,
-) -> () {
+ is_cancellable: bool
+) -> *mut JoinHandle<()> {
+ println!("Cancellable: {}", is_cancellable);
let client = unsafe { ffi_ref!(client_ptr) };
let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr) }.to_bytes())
.expect("Input should be a valid string")
@@ -48,7 +51,7 @@ pub extern "C" fn run_command_async(
// so we need to make our own copy.
.to_owned();
- client.runtime.spawn(async move {
+ let join_handle = client.runtime.spawn(async move {
let result = client.client.run_command(input_str.as_str()).await;
let str_result = match std::ffi::CString::new(result) {
Ok(cstr) => cstr.into_raw(),
@@ -61,6 +64,16 @@ pub extern "C" fn run_command_async(
let _ = CString::from_raw(str_result);
}
});
+
+ // We only want to box the join handle the caller
+ // has said that they may want to cancel, essentially
+ // promising to us that they will take care of the
+ // returned pointer.
+ if is_cancellable {
+ box_ptr!(join_handle)
+ } else {
+ std::ptr::null_mut()
+ }
}
// Init client, potential leak! You need to call free_mem after this!
@@ -91,3 +104,16 @@ pub extern "C" fn init(c_str_ptr: *const c_char) -> *mut CClient {
pub extern "C" fn free_mem(client_ptr: *mut CClient) {
std::mem::drop(unsafe { Box::from_raw(client_ptr) });
}
+
+#[no_mangle]
+pub extern "C" fn abort_and_free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
+ let join_handle = unsafe { Box::from_raw(join_handle_ptr) };
+ join_handle.abort();
+ println!("Freed handle");
+ std::mem::drop(join_handle);
+}
+
+#[no_mangle]
+pub extern "C" fn free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
+ std::mem::drop(unsafe { Box::from_raw(join_handle_ptr)});
+}
diff --git a/crates/bitwarden-core/Cargo.toml b/crates/bitwarden-core/Cargo.toml
index bb7a9b15e..465d02d86 100644
--- a/crates/bitwarden-core/Cargo.toml
+++ b/crates/bitwarden-core/Cargo.toml
@@ -48,6 +48,7 @@ serde_repr = ">=0.1.12, <0.2"
sha1 = ">=0.10.5, <0.11"
sha2 = ">=0.10.6, <0.11"
thiserror = ">=1.0.40, <2.0"
+tokio = { version = "1.36.0", features = ["rt", "macros"] }
uniffi = { version = "=0.28.1", optional = true, features = ["tokio"] }
uuid = { version = ">=1.3.3, <2.0", features = ["serde"] }
validator = { version = "0.18.1", features = ["derive"] }
@@ -71,7 +72,6 @@ reqwest = { version = ">=0.12.5, <0.13", features = [
[dev-dependencies]
bitwarden-crypto = { workspace = true }
rand_chacha = "0.3.1"
-tokio = { version = "1.36.0", features = ["rt", "macros"] }
wiremock = "0.6.0"
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
diff --git a/crates/bitwarden-core/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs
index 1f117d5fe..53a0a24e5 100644
--- a/crates/bitwarden-core/src/platform/client_platform.rs
+++ b/crates/bitwarden-core/src/platform/client_platform.rs
@@ -1,3 +1,5 @@
+use std::time::Duration;
+
use super::{
generate_fingerprint::{generate_fingerprint, generate_user_fingerprint},
get_user_api_key, FingerprintRequest, FingerprintResponse, SecretVerificationRequest,
@@ -24,6 +26,24 @@ impl<'a> ClientPlatform<'a> {
) -> Result {
get_user_api_key(self.client, &input).await
}
+
+ #[cfg(debug_assertions)]
+ pub async fn cancellation_test(&mut self, duration_millis: u64) -> Result {
+ tokio::time::sleep(Duration::from_millis(duration_millis)).await;
+ println!("After wait #1");
+ tokio::time::sleep(Duration::from_millis(duration_millis)).await;
+ println!("After wait #2");
+ tokio::time::sleep(Duration::from_millis(duration_millis)).await;
+ println!("After wait #3");
+ Ok(42)
+ }
+
+ #[cfg(debug_assertions)]
+ pub async fn error_test(&mut self) -> Result {
+ use crate::Error;
+
+ Err(Error::Internal(std::borrow::Cow::Borrowed("This is an error.")))
+ }
}
impl<'a> Client {
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index 50e46f6ce..ac2000d1a 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -98,6 +98,17 @@ impl Client {
client.generator().password(req).into_string()
}
},
+ #[cfg(debug_assertions)]
+ Command::Debug(cmd) => {
+ use crate::command::DebugCommand;
+ match cmd {
+ DebugCommand::CancellationTest { duration_millis } => {
+ client.platform().cancellation_test(duration_millis).await.into_string()
+ },
+ DebugCommand::ErrorTest { } => {
+ client.platform().error_test().await.into_string()
+ },
+ }},
}
}
diff --git a/crates/bitwarden-json/src/command.rs b/crates/bitwarden-json/src/command.rs
index 29dc79538..fe7078812 100644
--- a/crates/bitwarden-json/src/command.rs
+++ b/crates/bitwarden-json/src/command.rs
@@ -79,6 +79,8 @@ pub enum Command {
Projects(ProjectsCommand),
#[cfg(feature = "secrets")]
Generators(GeneratorsCommand),
+ #[cfg(debug_assertions)]
+ Debug(DebugCommand),
}
#[cfg(feature = "secrets")]
@@ -188,3 +190,11 @@ pub enum GeneratorsCommand {
/// Returns: [String]
GeneratePassword(PasswordGeneratorRequest),
}
+
+#[cfg(debug_assertions)]
+#[derive(Serialize, Deserialize, JsonSchema, Debug)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub enum DebugCommand {
+ CancellationTest { duration_millis: u64 },
+ ErrorTest { },
+}
diff --git a/languages/csharp/Bitwarden.Sdk.Tests/Bitwarden.Sdk.Tests.csproj b/languages/csharp/Bitwarden.Sdk.Tests/Bitwarden.Sdk.Tests.csproj
new file mode 100644
index 000000000..3dbd8c99e
--- /dev/null
+++ b/languages/csharp/Bitwarden.Sdk.Tests/Bitwarden.Sdk.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs b/languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
new file mode 100644
index 000000000..8c927eb74
--- /dev/null
+++ b/languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/languages/csharp/Bitwarden.Sdk.Tests/InteropTests.cs b/languages/csharp/Bitwarden.Sdk.Tests/InteropTests.cs
new file mode 100644
index 000000000..d6903d58d
--- /dev/null
+++ b/languages/csharp/Bitwarden.Sdk.Tests/InteropTests.cs
@@ -0,0 +1,35 @@
+using Bitwarden.Sdk;
+using System.Diagnostics;
+
+namespace Bitwarden.Sdk.Tests;
+
+public class InteropTests
+{
+ [Fact]
+ public async void CancelingTest_ThrowsTaskCanceledException()
+ {
+ var client = new BitwardenClient();
+
+ var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(250));
+
+ await Assert.ThrowsAsync(async () => await client.CancellationTestAsync(cts.Token));
+ }
+
+ [Fact]
+ public async void NoCancel_TaskCompletesSuccessfully()
+ {
+ var client = new BitwardenClient();
+
+ var result = await client.CancellationTestAsync(CancellationToken.None);
+ Assert.Equal(42, result);
+ }
+
+ [Fact]
+ public async void Error_ThrowsException()
+ {
+ var client = new BitwardenClient();
+
+ var bitwardenException = await Assert.ThrowsAsync(async () => await client.ErrorTestAsync());
+ Assert.Equal("Internal error: This is an error.", bitwardenException.Message);
+ }
+}
diff --git a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj
index 2d786a184..4a0934ab9 100644
--- a/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj
+++ b/languages/csharp/Bitwarden.Sdk/Bitwarden.Sdk.csproj
@@ -12,6 +12,8 @@
Bitwarden Inc.
SDK
+ true
+
https://github.com/bitwarden/sdk/tree/main/languages/csharp
Git
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs b/languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs
new file mode 100644
index 000000000..290cd5771
--- /dev/null
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs
@@ -0,0 +1,58 @@
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
+
+namespace Bitwarden.Sdk;
+
+[Obsolete("DebugCommand is intended for tests only, using any of these commands will throw errors in production code.")]
+[EditorBrowsable(EditorBrowsableState.Never)]
+partial class DebugCommand
+{
+
+}
+
+#if DEBUG
+public sealed partial class BitwardenClient
+{
+ public async Task CancellationTestAsync(CancellationToken token)
+ {
+ var result = await _commandRunner.RunCommandAsync(
+ new Command
+ {
+ Debug = new DebugCommand
+ {
+ CancellationTest = new CancellationTest
+ {
+ DurationMillis = 200,
+ },
+ },
+ }, token);
+
+ return ParseResult(result).GetInt32();
+ }
+
+ public async Task ErrorTestAsync()
+ {
+ var result = await _commandRunner.RunCommandAsync(
+ new Command
+ {
+ Debug = new DebugCommand
+ {
+ ErrorTest = new ErrorTest(),
+ },
+ });
+
+ return ParseResult(result).GetInt32();
+ }
+
+ private JsonElement ParseResult(JsonElement result)
+ {
+ if (result.GetProperty("success").GetBoolean())
+ {
+ return result.GetProperty("data");
+ }
+
+ throw new BitwardenException(result.GetProperty("errorMessage").GetString());
+ }
+}
+#endif
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs b/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs
index 2f10e0cf9..636a100b1 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenClient.cs
@@ -1,6 +1,8 @@
-namespace Bitwarden.Sdk;
+using System.Text.Json;
-public sealed class BitwardenClient : IDisposable
+namespace Bitwarden.Sdk;
+
+public sealed partial class BitwardenClient : IDisposable
{
private readonly CommandRunner _commandRunner;
private readonly BitwardenSafeHandle _handle;
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
index eb059ff2d..551494981 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
@@ -16,7 +16,16 @@ internal static partial class BitwardenLibrary
internal delegate void OnCompleteCallback(IntPtr json);
[LibraryImport("bitwarden_c", StringMarshalling = StringMarshalling.Utf8)]
- private static partial void run_command_async(string json, BitwardenSafeHandle handle, OnCompleteCallback on_completed_callback);
+ private static partial IntPtr run_command_async(string json,
+ BitwardenSafeHandle handle,
+ OnCompleteCallback onCompletedCallback,
+ [MarshalAs(UnmanagedType.U1)] bool isCancellable);
+
+ [LibraryImport("bitwarden_c", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void abort_and_free_handle(IntPtr joinHandle);
+
+ [LibraryImport("bitwarden_c", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial void free_handle(IntPtr joinHandle);
internal static BitwardenSafeHandle Init(string settings) => init(settings);
@@ -24,17 +33,26 @@ internal static partial class BitwardenLibrary
internal static string RunCommand(string json, BitwardenSafeHandle handle) => run_command(json, handle);
- internal static Task RunCommandAsync(string json, BitwardenSafeHandle handle, CancellationToken token = default)
+ internal static Task RunCommandAsync(string json, BitwardenSafeHandle handle, CancellationToken token)
{
+ token.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ IntPtr abortPointer = IntPtr.Zero;
+
try
{
- run_command_async(json, handle, (resultPointer) =>
+
+ abortPointer = run_command_async(json, handle, (resultPointer) =>
{
var stringResult = Marshal.PtrToStringUTF8(resultPointer);
tcs.SetResult(stringResult);
- });
+
+ if (abortPointer != IntPtr.Zero)
+ {
+ free_handle(abortPointer);
+ }
+ }, token.CanBeCanceled);
}
catch (Exception ex)
{
@@ -42,6 +60,11 @@ internal static Task RunCommandAsync(string json, BitwardenSafeHandle ha
}
// TODO: Register cancellation on token
+ token.Register((state) =>
+ {
+ abort_and_free_handle((IntPtr)state);
+ tcs.SetCanceled();
+ }, abortPointer);
return tcs.Task;
}
diff --git a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
index 41ecc3734..d757e69db 100644
--- a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
+++ b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
@@ -24,4 +24,10 @@ internal CommandRunner(BitwardenSafeHandle handle)
var result = await BitwardenLibrary.RunCommandAsync(req, _handle, cancellationToken);
return JsonSerializer.Deserialize(result, Converter.Settings);
}
+
+ internal async Task RunCommandAsync(string command, CancellationToken cancellationToken)
+ {
+ var result = await BitwardenLibrary.RunCommandAsync(command, _handle, cancellationToken);
+ return JsonSerializer.Deserialize(result, Converter.Settings);
+ }
}
diff --git a/languages/csharp/Bitwarden.sln b/languages/csharp/Bitwarden.sln
index 4cf8d147f..d57218979 100644
--- a/languages/csharp/Bitwarden.sln
+++ b/languages/csharp/Bitwarden.sln
@@ -1,9 +1,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
+#
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bitwarden.Sdk", "Bitwarden.Sdk\Bitwarden.Sdk.csproj", "{DADE59E5-E573-430A-8EB2-BC21D8E8C1D3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bitwarden.Sdk.Samples", "Bitwarden.Sdk.Samples\Bitwarden.Sdk.Samples.csproj", "{CA9F8EDC-643F-4624-AC00-F741E1F30CA4}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bitwarden.Sdk.Tests", "Bitwarden.Sdk.Tests\Bitwarden.Sdk.Tests.csproj", "{6E62CBBF-E9E6-4661-A3DC-D89C18E15A89}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -18,5 +21,9 @@ Global
{CA9F8EDC-643F-4624-AC00-F741E1F30CA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA9F8EDC-643F-4624-AC00-F741E1F30CA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA9F8EDC-643F-4624-AC00-F741E1F30CA4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E62CBBF-E9E6-4661-A3DC-D89C18E15A89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6E62CBBF-E9E6-4661-A3DC-D89C18E15A89}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E62CBBF-E9E6-4661-A3DC-D89C18E15A89}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E62CBBF-E9E6-4661-A3DC-D89C18E15A89}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
From 9ed29b76266f045c52ae6a40ad8047e3d70b3388 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 16:30:58 -0400
Subject: [PATCH 09/24] Remove Comment
---
languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
index 551494981..038f4fbb2 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
@@ -59,7 +59,6 @@ internal static Task RunCommandAsync(string json, BitwardenSafeHandle ha
tcs.SetException(ex);
}
- // TODO: Register cancellation on token
token.Register((state) =>
{
abort_and_free_handle((IntPtr)state);
From 6014b5179a2f1023c0f3700d7e4014fc06a99e66 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 16:40:53 -0400
Subject: [PATCH 10/24] Cleanup
---
crates/bitwarden-c/src/c.rs | 2 --
languages/csharp/Bitwarden.Sdk/AuthClient.cs | 4 +--
.../Bitwarden.Sdk/BitwardenClient.Debug.cs | 2 +-
.../csharp/Bitwarden.Sdk/BitwardenLibrary.cs | 10 ++++---
.../csharp/Bitwarden.Sdk/CommandRunner.cs | 2 +-
.../csharp/Bitwarden.Sdk/ProjectsClient.cs | 20 ++++++-------
.../csharp/Bitwarden.Sdk/SecretsClient.cs | 28 +++++++++----------
7 files changed, 34 insertions(+), 34 deletions(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index f1cb1c6f3..71ff83e2a 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -42,7 +42,6 @@ pub extern "C" fn run_command_async(
on_completed_callback: OnCompletedCallback,
is_cancellable: bool
) -> *mut JoinHandle<()> {
- println!("Cancellable: {}", is_cancellable);
let client = unsafe { ffi_ref!(client_ptr) };
let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr) }.to_bytes())
.expect("Input should be a valid string")
@@ -109,7 +108,6 @@ pub extern "C" fn free_mem(client_ptr: *mut CClient) {
pub extern "C" fn abort_and_free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
let join_handle = unsafe { Box::from_raw(join_handle_ptr) };
join_handle.abort();
- println!("Freed handle");
std::mem::drop(join_handle);
}
diff --git a/languages/csharp/Bitwarden.Sdk/AuthClient.cs b/languages/csharp/Bitwarden.Sdk/AuthClient.cs
index 3e148143a..7b7f25d2b 100644
--- a/languages/csharp/Bitwarden.Sdk/AuthClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/AuthClient.cs
@@ -9,10 +9,10 @@ internal AuthClient(CommandRunner commandRunner)
_commandRunner = commandRunner;
}
- public async Task LoginAccessTokenAsync(string accessToken, string stateFile = "")
+ public async Task LoginAccessTokenAsync(string accessToken, string stateFile = "", CancellationToken cancellationToken = default)
{
var command = new Command { LoginAccessToken = new AccessTokenLoginRequest { AccessToken = accessToken, StateFile = stateFile } };
- var response = await _commandRunner.RunCommandAsync(command);
+ var response = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (response is not { Success: true })
{
throw new BitwardenAuthException(response != null ? response.ErrorMessage : "Login failed");
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs b/languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs
index 290cd5771..0b1e95409 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenClient.Debug.cs
@@ -40,7 +40,7 @@ public async Task ErrorTestAsync()
{
ErrorTest = new ErrorTest(),
},
- });
+ }, CancellationToken.None);
return ParseResult(result).GetInt32();
}
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
index 038f4fbb2..b35089928 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
@@ -33,9 +33,9 @@ private static partial IntPtr run_command_async(string json,
internal static string RunCommand(string json, BitwardenSafeHandle handle) => run_command(json, handle);
- internal static Task RunCommandAsync(string json, BitwardenSafeHandle handle, CancellationToken token)
+ internal static Task RunCommandAsync(string json, BitwardenSafeHandle handle, CancellationToken cancellationToken)
{
- token.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
IntPtr abortPointer = IntPtr.Zero;
@@ -52,15 +52,17 @@ internal static Task RunCommandAsync(string json, BitwardenSafeHandle ha
{
free_handle(abortPointer);
}
- }, token.CanBeCanceled);
+ }, cancellationToken.CanBeCanceled);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
- token.Register((state) =>
+ cancellationToken.Register((state) =>
{
+ // This register delegate will never be called unless the token is cancelable
+ // therefore we know that the abortPointer is a valid pointer.
abort_and_free_handle((IntPtr)state);
tcs.SetCanceled();
}, abortPointer);
diff --git a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
index d757e69db..a62e0877e 100644
--- a/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
+++ b/languages/csharp/Bitwarden.Sdk/CommandRunner.cs
@@ -18,7 +18,7 @@ internal CommandRunner(BitwardenSafeHandle handle)
return JsonSerializer.Deserialize(result, Converter.Settings);
}
- internal async Task RunCommandAsync(Command command, CancellationToken cancellationToken = default)
+ internal async Task RunCommandAsync(Command command, CancellationToken cancellationToken)
{
var req = JsonSerializer.Serialize(command, Converter.Settings);
var result = await BitwardenLibrary.RunCommandAsync(req, _handle, cancellationToken);
diff --git a/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs b/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs
index e612b2e14..efa651518 100644
--- a/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/ProjectsClient.cs
@@ -9,10 +9,10 @@ internal ProjectsClient(CommandRunner commandRunner)
_commandRunner = commandRunner;
}
- public async Task GetAsync(Guid id)
+ public async Task GetAsync(Guid id, CancellationToken cancellationToken = default)
{
var command = new Command { Projects = new ProjectsCommand { Get = new ProjectGetRequest { Id = id } } };
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -22,7 +22,7 @@ public async Task GetAsync(Guid id)
throw new BitwardenException(result != null ? result.ErrorMessage : "Project not found");
}
- public async Task CreateAsync(Guid organizationId, string name)
+ public async Task CreateAsync(Guid organizationId, string name, CancellationToken cancellationToken = default)
{
var command = new Command
{
@@ -31,7 +31,7 @@ public async Task CreateAsync(Guid organizationId, string name)
Create = new ProjectCreateRequest { OrganizationId = organizationId, Name = name }
}
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -41,7 +41,7 @@ public async Task CreateAsync(Guid organizationId, string name)
throw new BitwardenException(result != null ? result.ErrorMessage : "Project create failed");
}
- public async Task UpdateAsync(Guid organizationId, Guid id, string name)
+ public async Task UpdateAsync(Guid organizationId, Guid id, string name, CancellationToken cancellationToken = default)
{
var command = new Command
{
@@ -50,7 +50,7 @@ public async Task UpdateAsync(Guid organizationId, Guid id, str
Update = new ProjectPutRequest { Id = id, OrganizationId = organizationId, Name = name }
}
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -60,13 +60,13 @@ public async Task UpdateAsync(Guid organizationId, Guid id, str
throw new BitwardenException(result != null ? result.ErrorMessage : "Project update failed");
}
- public async Task DeleteAsync(Guid[] ids)
+ public async Task DeleteAsync(Guid[] ids, CancellationToken cancellationToken = default)
{
var command = new Command
{
Projects = new ProjectsCommand { Delete = new ProjectsDeleteRequest { Ids = ids } }
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -76,13 +76,13 @@ public async Task DeleteAsync(Guid[] ids)
throw new BitwardenException(result != null ? result.ErrorMessage : "Project delete failed");
}
- public async Task ListAsync(Guid organizationId)
+ public async Task ListAsync(Guid organizationId, CancellationToken cancellationToken = default)
{
var command = new Command
{
Projects = new ProjectsCommand { List = new ProjectsListRequest { OrganizationId = organizationId } }
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
diff --git a/languages/csharp/Bitwarden.Sdk/SecretsClient.cs b/languages/csharp/Bitwarden.Sdk/SecretsClient.cs
index 743c92a81..e5e8cbc26 100644
--- a/languages/csharp/Bitwarden.Sdk/SecretsClient.cs
+++ b/languages/csharp/Bitwarden.Sdk/SecretsClient.cs
@@ -9,10 +9,10 @@ internal SecretsClient(CommandRunner commandRunner)
_commandRunner = commandRunner;
}
- public async Task GetAsync(Guid id)
+ public async Task GetAsync(Guid id, CancellationToken cancellationToken = default)
{
var command = new Command { Secrets = new SecretsCommand { Get = new SecretGetRequest { Id = id } } };
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -22,10 +22,10 @@ public async Task GetAsync(Guid id)
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret not found");
}
- public async Task GetByIdsAsync(Guid[] ids)
+ public async Task GetByIdsAsync(Guid[] ids, CancellationToken cancellationToken = default)
{
var command = new Command { Secrets = new SecretsCommand { GetByIds = new SecretsGetRequest { Ids = ids } } };
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -35,7 +35,7 @@ public async Task GetByIdsAsync(Guid[] ids)
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret not found");
}
- public async Task CreateAsync(Guid organizationId, string key, string value, string note, Guid[] projectIds)
+ public async Task CreateAsync(Guid organizationId, string key, string value, string note, Guid[] projectIds, CancellationToken cancellationToken = default)
{
var command = new Command
{
@@ -52,7 +52,7 @@ public async Task CreateAsync(Guid organizationId, string key, s
}
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -62,7 +62,7 @@ public async Task CreateAsync(Guid organizationId, string key, s
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret create failed");
}
- public async Task UpdateAsync(Guid organizationId, Guid id, string key, string value, string note, Guid[] projectIds)
+ public async Task UpdateAsync(Guid organizationId, Guid id, string key, string value, string note, Guid[] projectIds, CancellationToken cancellationToken = default)
{
var command = new Command
{
@@ -80,7 +80,7 @@ public async Task UpdateAsync(Guid organizationId, Guid id, stri
}
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -90,10 +90,10 @@ public async Task UpdateAsync(Guid organizationId, Guid id, stri
throw new BitwardenException(result != null ? result.ErrorMessage : "Secret update failed");
}
- public async Task DeleteAsync(Guid[] ids)
+ public async Task DeleteAsync(Guid[] ids, CancellationToken cancellationToken = default)
{
var command = new Command { Secrets = new SecretsCommand { Delete = new SecretsDeleteRequest { Ids = ids } } };
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -103,13 +103,13 @@ public async Task DeleteAsync(Guid[] ids)
throw new BitwardenException(result != null ? result.ErrorMessage : "Secrets delete failed");
}
- public async Task ListAsync(Guid organizationId)
+ public async Task ListAsync(Guid organizationId, CancellationToken cancellationToken = default)
{
var command = new Command
{
Secrets = new SecretsCommand { List = new SecretIdentifiersRequest { OrganizationId = organizationId } }
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
@@ -119,7 +119,7 @@ public async Task ListAsync(Guid organizationId)
throw new BitwardenException(result != null ? result.ErrorMessage : "No secrets for given organization");
}
- public async Task SyncAsync(Guid organizationId, DateTimeOffset? lastSyncedDate)
+ public async Task SyncAsync(Guid organizationId, DateTimeOffset? lastSyncedDate, CancellationToken cancellationToken = default)
{
var command = new Command
{
@@ -133,7 +133,7 @@ public async Task SyncAsync(Guid organizationId, DateTimeOf
}
};
- var result = await _commandRunner.RunCommandAsync(command);
+ var result = await _commandRunner.RunCommandAsync(command, cancellationToken);
if (result is { Success: true })
{
From cb7371584a6ff640515bfde9ef1439320bf9bba6 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 16:51:16 -0400
Subject: [PATCH 11/24] Formatting
---
crates/bitwarden-c/src/c.rs | 10 +++++-----
crates/bitwarden-core/src/platform/client_platform.rs | 4 +++-
crates/bitwarden-json/src/client.rs | 8 +++++---
crates/bitwarden-json/src/command.rs | 2 +-
4 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index 71ff83e2a..4da6467d2 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -1,7 +1,7 @@
use std::{
ffi::{CStr, CString},
os::raw::c_char,
- str
+ str,
};
use bitwarden_json::client::Client;
@@ -40,7 +40,7 @@ pub extern "C" fn run_command_async(
c_str_ptr: *const c_char,
client_ptr: *const CClient,
on_completed_callback: OnCompletedCallback,
- is_cancellable: bool
+ is_cancellable: bool,
) -> *mut JoinHandle<()> {
let client = unsafe { ffi_ref!(client_ptr) };
let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr) }.to_bytes())
@@ -105,13 +105,13 @@ pub extern "C" fn free_mem(client_ptr: *mut CClient) {
}
#[no_mangle]
-pub extern "C" fn abort_and_free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
+pub extern "C" fn abort_and_free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
let join_handle = unsafe { Box::from_raw(join_handle_ptr) };
join_handle.abort();
std::mem::drop(join_handle);
}
#[no_mangle]
-pub extern "C" fn free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
- std::mem::drop(unsafe { Box::from_raw(join_handle_ptr)});
+pub extern "C" fn free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
+ std::mem::drop(unsafe { Box::from_raw(join_handle_ptr) });
}
diff --git a/crates/bitwarden-core/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs
index 53a0a24e5..7cd9032c1 100644
--- a/crates/bitwarden-core/src/platform/client_platform.rs
+++ b/crates/bitwarden-core/src/platform/client_platform.rs
@@ -42,7 +42,9 @@ impl<'a> ClientPlatform<'a> {
pub async fn error_test(&mut self) -> Result {
use crate::Error;
- Err(Error::Internal(std::borrow::Cow::Borrowed("This is an error.")))
+ Err(Error::Internal(std::borrow::Cow::Borrowed(
+ "This is an error.",
+ )))
}
}
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index ac2000d1a..5e66c4f23 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -102,9 +102,11 @@ impl Client {
Command::Debug(cmd) => {
use crate::command::DebugCommand;
match cmd {
- DebugCommand::CancellationTest { duration_millis } => {
- client.platform().cancellation_test(duration_millis).await.into_string()
- },
+ DebugCommand::CancellationTest { duration_millis } => client
+ .platform()
+ .cancellation_test(duration_millis)
+ .await
+ .into_string(),
DebugCommand::ErrorTest { } => {
client.platform().error_test().await.into_string()
},
diff --git a/crates/bitwarden-json/src/command.rs b/crates/bitwarden-json/src/command.rs
index fe7078812..26a3471bd 100644
--- a/crates/bitwarden-json/src/command.rs
+++ b/crates/bitwarden-json/src/command.rs
@@ -196,5 +196,5 @@ pub enum GeneratorsCommand {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum DebugCommand {
CancellationTest { duration_millis: u64 },
- ErrorTest { },
+ ErrorTest {},
}
From c1dc017e627ce2c1e831555fd99e62deaf36d459 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 16:53:29 -0400
Subject: [PATCH 12/24] More Formatting
---
crates/bitwarden-c/src/c.rs | 2 +-
crates/bitwarden-json/src/client.rs | 7 ++++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index 4da6467d2..c5cfaa1dd 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -112,6 +112,6 @@ pub extern "C" fn abort_and_free_handle(join_handle_ptr: *mut tokio::task::JoinH
}
#[no_mangle]
-pub extern "C" fn free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
+pub extern "C" fn free_handle(join_handle_ptr: *mut tokio::task::JoinHandle<()>) -> () {
std::mem::drop(unsafe { Box::from_raw(join_handle_ptr) });
}
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index 5e66c4f23..946ce7c10 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -107,10 +107,11 @@ impl Client {
.cancellation_test(duration_millis)
.await
.into_string(),
- DebugCommand::ErrorTest { } => {
+ DebugCommand::ErrorTest {} => {
client.platform().error_test().await.into_string()
- },
- }},
+ }
+ }
+ }
}
}
From 5787db5031dce047b7bfc2c38591d1abec44f40d Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 16:57:06 -0400
Subject: [PATCH 13/24] Use More Local Import
---
crates/bitwarden-core/src/platform/client_platform.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/crates/bitwarden-core/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs
index 7cd9032c1..cda951c73 100644
--- a/crates/bitwarden-core/src/platform/client_platform.rs
+++ b/crates/bitwarden-core/src/platform/client_platform.rs
@@ -1,5 +1,3 @@
-use std::time::Duration;
-
use super::{
generate_fingerprint::{generate_fingerprint, generate_user_fingerprint},
get_user_api_key, FingerprintRequest, FingerprintResponse, SecretVerificationRequest,
@@ -29,6 +27,8 @@ impl<'a> ClientPlatform<'a> {
#[cfg(debug_assertions)]
pub async fn cancellation_test(&mut self, duration_millis: u64) -> Result {
+ use std::time::Duration;
+
tokio::time::sleep(Duration::from_millis(duration_millis)).await;
println!("After wait #1");
tokio::time::sleep(Duration::from_millis(duration_millis)).await;
From d28a9e2c943638d403bff00d5fa7e9d0d2ba12cb Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 17:00:20 -0400
Subject: [PATCH 14/24] Remove Unnecessary async
---
crates/bitwarden-core/src/platform/client_platform.rs | 2 +-
crates/bitwarden-json/src/client.rs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/crates/bitwarden-core/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs
index cda951c73..99415dbc3 100644
--- a/crates/bitwarden-core/src/platform/client_platform.rs
+++ b/crates/bitwarden-core/src/platform/client_platform.rs
@@ -39,7 +39,7 @@ impl<'a> ClientPlatform<'a> {
}
#[cfg(debug_assertions)]
- pub async fn error_test(&mut self) -> Result {
+ pub fn error_test(&mut self) -> Result {
use crate::Error;
Err(Error::Internal(std::borrow::Cow::Borrowed(
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index 946ce7c10..4f76aab8c 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -108,7 +108,7 @@ impl Client {
.await
.into_string(),
DebugCommand::ErrorTest {} => {
- client.platform().error_test().await.into_string()
+ client.platform().error_test().into_string()
}
}
}
From 3709f6d44e4751378d3d6ae71a4ad4ad170d5699 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 17:01:37 -0400
Subject: [PATCH 15/24] Format
---
crates/bitwarden-json/src/client.rs | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index 4f76aab8c..cd4513d5d 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -107,9 +107,7 @@ impl Client {
.cancellation_test(duration_millis)
.await
.into_string(),
- DebugCommand::ErrorTest {} => {
- client.platform().error_test().into_string()
- }
+ DebugCommand::ErrorTest {} => client.platform().error_test().into_string()
}
}
}
From d1019148da3a18de81e4d179791c85ff407ce404 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 17:04:45 -0400
Subject: [PATCH 16/24] Format Again...
---
crates/bitwarden-json/src/client.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index cd4513d5d..244ea3d09 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -107,7 +107,7 @@ impl Client {
.cancellation_test(duration_millis)
.await
.into_string(),
- DebugCommand::ErrorTest {} => client.platform().error_test().into_string()
+ DebugCommand::ErrorTest {} => client.platform().error_test().into_string(),
}
}
}
From 5c433eb7635ad54666f2435144c0626c9d0c71d6 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 4 Sep 2024 21:12:41 -0400
Subject: [PATCH 17/24] Try A Thing
---
crates/bitwarden-core/Cargo.toml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/crates/bitwarden-core/Cargo.toml b/crates/bitwarden-core/Cargo.toml
index 465d02d86..5c642cce9 100644
--- a/crates/bitwarden-core/Cargo.toml
+++ b/crates/bitwarden-core/Cargo.toml
@@ -48,13 +48,15 @@ serde_repr = ">=0.1.12, <0.2"
sha1 = ">=0.10.5, <0.11"
sha2 = ">=0.10.6, <0.11"
thiserror = ">=1.0.40, <2.0"
-tokio = { version = "1.36.0", features = ["rt", "macros"] }
uniffi = { version = "=0.28.1", optional = true, features = ["tokio"] }
uuid = { version = ">=1.3.3, <2.0", features = ["serde"] }
validator = { version = "0.18.1", features = ["derive"] }
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
zxcvbn = { version = ">=3.0.1, <4.0", optional = true }
+[target.'cfg(debug_assertions)'.dependencies]
+tokio = { version = "1.36.0", features = ["rt", "macros", "time"] }
+
[target.'cfg(all(not(target_arch="wasm32"), not(windows)))'.dependencies]
# By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates
# The only exception is WASM, as it just uses the browsers/node fetch
From ec3004d22c2848a9e8c154f4051ef2529196a157 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Thu, 5 Sep 2024 19:49:33 -0400
Subject: [PATCH 18/24] Pass `CancellationToken` to `SetCancelled`
---
languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
index b35089928..e18d3976b 100644
--- a/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
+++ b/languages/csharp/Bitwarden.Sdk/BitwardenLibrary.cs
@@ -64,7 +64,7 @@ internal static Task RunCommandAsync(string json, BitwardenSafeHandle ha
// This register delegate will never be called unless the token is cancelable
// therefore we know that the abortPointer is a valid pointer.
abort_and_free_handle((IntPtr)state);
- tcs.SetCanceled();
+ tcs.SetCanceled(cancellationToken);
}, abortPointer);
return tcs.Task;
From 447d6e76688c1995c5b23f4561a2e051a6e80c01 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 10 Sep 2024 12:35:36 -0400
Subject: [PATCH 19/24] Update crates/bitwarden-c/src/c.rs
Co-authored-by: Oscar Hinton
---
crates/bitwarden-c/src/c.rs | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/crates/bitwarden-c/src/c.rs b/crates/bitwarden-c/src/c.rs
index c5cfaa1dd..76fed0073 100644
--- a/crates/bitwarden-c/src/c.rs
+++ b/crates/bitwarden-c/src/c.rs
@@ -64,10 +64,8 @@ pub extern "C" fn run_command_async(
}
});
- // We only want to box the join handle the caller
- // has said that they may want to cancel, essentially
- // promising to us that they will take care of the
- // returned pointer.
+ // We only want to box the join handle the caller has said that they may want to cancel,
+ // essentially promising to us that they will take care of the returned pointer.
if is_cancellable {
box_ptr!(join_handle)
} else {
From 7bcd6d768896b0fcbde69d51f571577b5057ab6d Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 10 Sep 2024 12:51:46 -0400
Subject: [PATCH 20/24] Update
languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
Co-authored-by: Oscar Hinton
---
languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs b/languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
index 8c927eb74..c802f4480 100644
--- a/languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
+++ b/languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
@@ -1 +1 @@
-global using Xunit;
\ No newline at end of file
+global using Xunit;
From 8b16ecea4d2724fdcd8872edfc888c4fe106aceb Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 10 Sep 2024 13:28:10 -0400
Subject: [PATCH 21/24] Move Debug Stuff To bitwarden-json
---
Cargo.lock | 1 +
crates/bitwarden-core/Cargo.toml | 4 +--
.../src/platform/client_platform.rs | 22 ----------------
crates/bitwarden-json/Cargo.toml | 3 +++
crates/bitwarden-json/src/client.rs | 26 ++++++++++++++-----
5 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 885aad0cb..5818f6cfc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -560,6 +560,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
+ "tokio",
]
[[package]]
diff --git a/crates/bitwarden-core/Cargo.toml b/crates/bitwarden-core/Cargo.toml
index 5c642cce9..0eab40241 100644
--- a/crates/bitwarden-core/Cargo.toml
+++ b/crates/bitwarden-core/Cargo.toml
@@ -54,9 +54,6 @@ validator = { version = "0.18.1", features = ["derive"] }
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
zxcvbn = { version = ">=3.0.1, <4.0", optional = true }
-[target.'cfg(debug_assertions)'.dependencies]
-tokio = { version = "1.36.0", features = ["rt", "macros", "time"] }
-
[target.'cfg(all(not(target_arch="wasm32"), not(windows)))'.dependencies]
# By default, we use rustls as the TLS stack and rust-platform-verifier to support user-installed root certificates
# The only exception is WASM, as it just uses the browsers/node fetch
@@ -76,6 +73,7 @@ bitwarden-crypto = { workspace = true }
rand_chacha = "0.3.1"
wiremock = "0.6.0"
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
+tokio = { version = "1.36.0", features = ["rt", "macros"] }
[lints]
workspace = true
diff --git a/crates/bitwarden-core/src/platform/client_platform.rs b/crates/bitwarden-core/src/platform/client_platform.rs
index 99415dbc3..1f117d5fe 100644
--- a/crates/bitwarden-core/src/platform/client_platform.rs
+++ b/crates/bitwarden-core/src/platform/client_platform.rs
@@ -24,28 +24,6 @@ impl<'a> ClientPlatform<'a> {
) -> Result {
get_user_api_key(self.client, &input).await
}
-
- #[cfg(debug_assertions)]
- pub async fn cancellation_test(&mut self, duration_millis: u64) -> Result {
- use std::time::Duration;
-
- tokio::time::sleep(Duration::from_millis(duration_millis)).await;
- println!("After wait #1");
- tokio::time::sleep(Duration::from_millis(duration_millis)).await;
- println!("After wait #2");
- tokio::time::sleep(Duration::from_millis(duration_millis)).await;
- println!("After wait #3");
- Ok(42)
- }
-
- #[cfg(debug_assertions)]
- pub fn error_test(&mut self) -> Result {
- use crate::Error;
-
- Err(Error::Internal(std::borrow::Cow::Borrowed(
- "This is an error.",
- )))
- }
}
impl<'a> Client {
diff --git a/crates/bitwarden-json/Cargo.toml b/crates/bitwarden-json/Cargo.toml
index 69ca8e6ab..095c02402 100644
--- a/crates/bitwarden-json/Cargo.toml
+++ b/crates/bitwarden-json/Cargo.toml
@@ -26,5 +26,8 @@ schemars = ">=0.8.12, <0.9"
serde = { version = ">=1.0, <2.0", features = ["derive"] }
serde_json = ">=1.0.96, <2.0"
+[target.'cfg(debug_assertions)'.dependencies]
+tokio = { version = "1.36.0", features = ["time"] }
+
[lints]
workspace = true
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index 244ea3d09..7a4d2789f 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -101,13 +101,27 @@ impl Client {
#[cfg(debug_assertions)]
Command::Debug(cmd) => {
use crate::command::DebugCommand;
+ use bitwarden::Error;
+
match cmd {
- DebugCommand::CancellationTest { duration_millis } => client
- .platform()
- .cancellation_test(duration_millis)
- .await
- .into_string(),
- DebugCommand::ErrorTest {} => client.platform().error_test().into_string(),
+ DebugCommand::CancellationTest { duration_millis } => {
+ use tokio::time::sleep;
+ let duration = std::time::Duration::from_millis(duration_millis);
+ sleep(duration).await;
+ println!("After wait #1");
+ sleep(duration).await;
+ println!("After wait #2");
+ sleep(duration).await;
+ println!("After wait #3");
+ Ok::(42).into_string()
+ },
+ DebugCommand::ErrorTest {} => {
+ use bitwarden::Error;
+
+ Err::(Error::Internal(std::borrow::Cow::Borrowed(
+ "This is an error.",
+ ))).into_string()
+ },
}
}
}
From 78a1ad675e9b4bd2415556408aedff7020f918d4 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 10 Sep 2024 13:34:36 -0400
Subject: [PATCH 22/24] Reorder Deps
---
crates/bitwarden-core/Cargo.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/bitwarden-core/Cargo.toml b/crates/bitwarden-core/Cargo.toml
index 0eab40241..bb7a9b15e 100644
--- a/crates/bitwarden-core/Cargo.toml
+++ b/crates/bitwarden-core/Cargo.toml
@@ -71,9 +71,9 @@ reqwest = { version = ">=0.12.5, <0.13", features = [
[dev-dependencies]
bitwarden-crypto = { workspace = true }
rand_chacha = "0.3.1"
+tokio = { version = "1.36.0", features = ["rt", "macros"] }
wiremock = "0.6.0"
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
-tokio = { version = "1.36.0", features = ["rt", "macros"] }
[lints]
workspace = true
From 38c7cc57da3a554ca5cf07d42c27092bfa6cb148 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 10 Sep 2024 13:44:27 -0400
Subject: [PATCH 23/24] Format
---
crates/bitwarden-json/src/client.rs | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/crates/bitwarden-json/src/client.rs b/crates/bitwarden-json/src/client.rs
index 7a4d2789f..71f57cc3d 100644
--- a/crates/bitwarden-json/src/client.rs
+++ b/crates/bitwarden-json/src/client.rs
@@ -100,9 +100,10 @@ impl Client {
},
#[cfg(debug_assertions)]
Command::Debug(cmd) => {
- use crate::command::DebugCommand;
use bitwarden::Error;
+ use crate::command::DebugCommand;
+
match cmd {
DebugCommand::CancellationTest { duration_millis } => {
use tokio::time::sleep;
@@ -114,14 +115,15 @@ impl Client {
sleep(duration).await;
println!("After wait #3");
Ok::(42).into_string()
- },
+ }
DebugCommand::ErrorTest {} => {
use bitwarden::Error;
Err::(Error::Internal(std::borrow::Cow::Borrowed(
"This is an error.",
- ))).into_string()
- },
+ )))
+ .into_string()
+ }
}
}
}
From 0c8566b1ee07f89ed303300917718431667e7bf5 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 17 Sep 2024 20:02:06 -0400
Subject: [PATCH 24/24] Add Sample Tests
---
.../csharp/Bitwarden.Sdk.Tests/SampleTests.cs | 56 +++++++++++++++++++
.../Bitwarden.Sdk.Tests/SecretsManagerFact.cs | 54 ++++++++++++++++++
2 files changed, 110 insertions(+)
create mode 100644 languages/csharp/Bitwarden.Sdk.Tests/SampleTests.cs
create mode 100644 languages/csharp/Bitwarden.Sdk.Tests/SecretsManagerFact.cs
diff --git a/languages/csharp/Bitwarden.Sdk.Tests/SampleTests.cs b/languages/csharp/Bitwarden.Sdk.Tests/SampleTests.cs
new file mode 100644
index 000000000..6432f9ea1
--- /dev/null
+++ b/languages/csharp/Bitwarden.Sdk.Tests/SampleTests.cs
@@ -0,0 +1,56 @@
+namespace Bitwarden.Sdk.Tests;
+
+public class SampleTests
+{
+ [SecretsManagerFact]
+ public async Task RunSample_Works()
+ {
+ // Get environment variables
+ var identityUrl = Environment.GetEnvironmentVariable("IDENTITY_URL")!;
+ var apiUrl = Environment.GetEnvironmentVariable("API_URL")!;
+ var organizationId = Guid.Parse(Environment.GetEnvironmentVariable("ORGANIZATION_ID")!);
+ var accessToken = Environment.GetEnvironmentVariable("ACCESS_TOKEN")!;
+ var stateFile = Environment.GetEnvironmentVariable("STATE_FILE")!;
+
+ // Create the SDK Client
+ using var bitwardenClient = new BitwardenClient(new BitwardenSettings
+ {
+ ApiUrl = apiUrl,
+ IdentityUrl = identityUrl
+ });
+
+ // Authenticate
+ await bitwardenClient.Auth.LoginAccessTokenAsync(accessToken, stateFile);
+
+ // Projects Create, Update, & Get
+ var projectResponse = await bitwardenClient.Projects.CreateAsync(organizationId, "NewTestProject");
+ projectResponse = await bitwardenClient.Projects.UpdateAsync(organizationId, projectResponse.Id, "NewTestProject Renamed");
+ projectResponse = await bitwardenClient.Projects.GetAsync(projectResponse.Id);
+
+ Assert.Equal("NewTestProject Renamed", projectResponse.Name);
+
+ var projectList = await bitwardenClient.Projects.ListAsync(organizationId);
+
+ Assert.True(projectList.Data.Count() >= 1);
+
+ // Secrets list
+ var secretsList = await bitwardenClient.Secrets.ListAsync(organizationId);
+
+ // Secrets Create, Update, Get
+ var secretResponse = await bitwardenClient.Secrets.CreateAsync(organizationId, "New Secret", "the secret value", "the secret note", new[] { projectResponse.Id });
+ secretResponse = await bitwardenClient.Secrets.UpdateAsync(organizationId, secretResponse.Id, "New Secret Name", "the secret value", "the secret note", new[] { projectResponse.Id });
+ secretResponse = await bitwardenClient.Secrets.GetAsync(secretResponse.Id);
+
+ Assert.Equal("New Secret Name", secretResponse.Key);
+
+ // Secrets GetByIds
+ var secretsResponse = await bitwardenClient.Secrets.GetByIdsAsync(new[] { secretResponse.Id });
+
+ // Secrets Sync
+ var syncResponse = await bitwardenClient.Secrets.SyncAsync(organizationId, null);
+
+ // Secrets & Projects Delete
+ await bitwardenClient.Secrets.DeleteAsync(new[] { secretResponse.Id });
+ await bitwardenClient.Projects.DeleteAsync(new[] { projectResponse.Id });
+ }
+}
diff --git a/languages/csharp/Bitwarden.Sdk.Tests/SecretsManagerFact.cs b/languages/csharp/Bitwarden.Sdk.Tests/SecretsManagerFact.cs
new file mode 100644
index 000000000..e5bcebfc2
--- /dev/null
+++ b/languages/csharp/Bitwarden.Sdk.Tests/SecretsManagerFact.cs
@@ -0,0 +1,54 @@
+namespace Bitwarden.Sdk.Tests;
+
+public class SecretsManagerFactAttribute : FactAttribute
+{
+ public SecretsManagerFactAttribute()
+ {
+ if (!TryGetEnvironment("IDENTITY_URL", out var identityUrl))
+ {
+ Skip = "Environment variable IDENTITY_URL was not provided.";
+ }
+
+ if (!Uri.TryCreate(identityUrl, UriKind.Absolute, out _))
+ {
+ Skip = $"The identity url {identityUrl} provided in IDENTITY_URL is not a valid URL.";
+ }
+
+ if (!TryGetEnvironment("API_URL", out var apiUrl))
+ {
+ Skip = "Environment variable API_URL was not provided.";
+ }
+
+ if (!Uri.TryCreate(apiUrl, UriKind.Absolute, out _))
+ {
+ Skip = $"The identity url {apiUrl} provided in API_URL is not a valid URL.";
+ }
+
+ if (!TryGetEnvironment("ORGANIZATION_ID", out var organizationId))
+ {
+ Skip = "Environment variable ORGANIZATION_ID was not provided.";
+ }
+
+ if (!Guid.TryParse(organizationId, out _))
+ {
+ Skip = $"The organization id {organizationId} provided in ORGANIZATION_ID is not a valid GUID.";
+ }
+
+ if (!TryGetEnvironment("ACCESS_TOKEN", out _))
+ {
+ Skip = "Environment variable ACCESS_TOKEN was not provided.";
+ }
+ }
+
+ private static bool TryGetEnvironment(string variable, out string value)
+ {
+ value = Environment.GetEnvironmentVariable(variable);
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return false;
+ }
+
+ return true;
+ }
+}