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; + } +}