Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add run_command_async #993

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
520a958
Add `run_command_async`
justindbaur Aug 23, 2024
a913189
Remove Logs
justindbaur Aug 23, 2024
497979e
Formatting
justindbaur Aug 23, 2024
c5c8d17
Formatting
justindbaur Aug 23, 2024
2d2801b
Free String On Rust Side
justindbaur Aug 23, 2024
adb445f
Formatting
justindbaur Aug 25, 2024
60bf783
Merge remote-tracking branch 'origin/main' into add-run-command-async
justindbaur Sep 3, 2024
a58121a
Migrate All C# Clients to Async
justindbaur Sep 4, 2024
bb1bc1e
Support Cancellation
justindbaur Sep 4, 2024
9ed29b7
Remove Comment
justindbaur Sep 4, 2024
6014b51
Cleanup
justindbaur Sep 4, 2024
cb73715
Formatting
justindbaur Sep 4, 2024
c1dc017
More Formatting
justindbaur Sep 4, 2024
5787db5
Use More Local Import
justindbaur Sep 4, 2024
d28a9e2
Remove Unnecessary async
justindbaur Sep 4, 2024
3709f6d
Format
justindbaur Sep 4, 2024
d101914
Format Again...
justindbaur Sep 4, 2024
5c433eb
Try A Thing
justindbaur Sep 5, 2024
ec3004d
Pass `CancellationToken` to `SetCancelled`
justindbaur Sep 5, 2024
447d6e7
Update crates/bitwarden-c/src/c.rs
justindbaur Sep 10, 2024
7bcd6d7
Update languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
justindbaur Sep 10, 2024
8b16ece
Move Debug Stuff To bitwarden-json
justindbaur Sep 10, 2024
9d3a052
Merge branch 'add-run-command-async' of github.com:bitwarden/sdk intoโ€ฆ
justindbaur Sep 10, 2024
78a1ad6
Reorder Deps
justindbaur Sep 10, 2024
38c7cc5
Format
justindbaur Sep 10, 2024
0c8566b
Add Sample Tests
justindbaur Sep 18, 2024
aaff4f5
Merge branch 'main' into add-run-command-async
justindbaur Oct 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 58 additions & 1 deletion crates/bitwarden-c/src/c.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use std::{ffi::CStr, os::raw::c_char, str};
use std::{
ffi::{CStr, CString},
os::raw::c_char,
str,
};

use bitwarden_json::client::Client;
use tokio::task::JoinHandle;

use crate::{box_ptr, ffi_ref};

Expand Down Expand Up @@ -28,6 +33,46 @@
}
}

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,
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())
.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();

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(),
Err(_) => panic!("failed to return comment result: null encountered"),

Check warning on line 57 in crates/bitwarden-c/src/c.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-c/src/c.rs#L39-L57

Added lines #L39 - L57 were not covered by tests
};

// run completed function
unsafe {
on_completed_callback(str_result);
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)

Check warning on line 70 in crates/bitwarden-c/src/c.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-c/src/c.rs#L61-L70

Added lines #L61 - L70 were not covered by tests
} else {
std::ptr::null_mut()

Check warning on line 72 in crates/bitwarden-c/src/c.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-c/src/c.rs#L72

Added line #L72 was not covered by tests
}
}

Check warning on line 74 in crates/bitwarden-c/src/c.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-c/src/c.rs#L74

Added line #L74 was not covered by tests

// 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 {
Expand Down Expand Up @@ -56,3 +101,15 @@
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();
std::mem::drop(join_handle);
}

Check warning on line 110 in crates/bitwarden-c/src/c.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-c/src/c.rs#L106-L110

Added lines #L106 - L110 were not covered by tests

#[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) });
}

Check warning on line 115 in crates/bitwarden-c/src/c.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-c/src/c.rs#L113-L115

Added lines #L113 - L115 were not covered by tests
3 changes: 3 additions & 0 deletions crates/bitwarden-json/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 28 additions & 0 deletions crates/bitwarden-json/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,34 @@
client.generator().password(req).into_string()
}
},
#[cfg(debug_assertions)]
Command::Debug(cmd) => {
use bitwarden::Error;

Check warning on line 103 in crates/bitwarden-json/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-json/src/client.rs#L103

Added line #L103 was not covered by tests

use crate::command::DebugCommand;

match cmd {
DebugCommand::CancellationTest { duration_millis } => {
use tokio::time::sleep;

Check warning on line 109 in crates/bitwarden-json/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-json/src/client.rs#L108-L109

Added lines #L108 - L109 were not covered by tests
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::<i32, Error>(42).into_string()
}

Check warning on line 118 in crates/bitwarden-json/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-json/src/client.rs#L111-L118

Added lines #L111 - L118 were not covered by tests
DebugCommand::ErrorTest {} => {
use bitwarden::Error;

Err::<i32, Error>(Error::Internal(std::borrow::Cow::Borrowed(
"This is an error.",
)))
.into_string()
}

Check warning on line 126 in crates/bitwarden-json/src/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-json/src/client.rs#L123-L126

Added lines #L123 - L126 were not covered by tests
}
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions crates/bitwarden-json/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
Projects(ProjectsCommand),
#[cfg(feature = "secrets")]
Generators(GeneratorsCommand),
#[cfg(debug_assertions)]
Debug(DebugCommand),
}

#[cfg(feature = "secrets")]
Expand Down Expand Up @@ -188,3 +190,11 @@
/// Returns: [String]
GeneratePassword(PasswordGeneratorRequest),
}

#[cfg(debug_assertions)]
#[derive(Serialize, Deserialize, JsonSchema, Debug)]

Check warning on line 195 in crates/bitwarden-json/src/command.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-json/src/command.rs#L195

Added line #L195 was not covered by tests
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum DebugCommand {
CancellationTest { duration_millis: u64 },
ErrorTest {},
}
Comment on lines +194 to +200
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative way. of doing this would be to add one or two separate commands to bitwarden-c.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll let @bitwarden/team-secrets-manager-dev decide if they want to accept net 8 as the new target.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
28 changes: 14 additions & 14 deletions languages/csharp/Bitwarden.Sdk.Samples/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
๏ปฟusing Bitwarden.Sdk;
using Bitwarden.Sdk;

// Get environment variables
var identityUrl = Environment.GetEnvironmentVariable("IDENTITY_URL")!;
Expand All @@ -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)
{
Expand All @@ -30,17 +30,17 @@

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

Console.Write("Press enter to continue...");
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)
{
Expand All @@ -52,22 +52,22 @@

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

Console.Write("Press enter to continue...");
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 });
29 changes: 29 additions & 0 deletions languages/csharp/Bitwarden.Sdk.Tests/Bitwarden.Sdk.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Bitwarden.Sdk\Bitwarden.Sdk.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions languages/csharp/Bitwarden.Sdk.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
35 changes: 35 additions & 0 deletions languages/csharp/Bitwarden.Sdk.Tests/InteropTests.cs
Original file line number Diff line number Diff line change
@@ -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<TaskCanceledException>(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<BitwardenException>(async () => await client.ErrorTestAsync());
Assert.Equal("Internal error: This is an error.", bitwardenException.Message);
}
}
56 changes: 56 additions & 0 deletions languages/csharp/Bitwarden.Sdk.Tests/SampleTests.cs
Original file line number Diff line number Diff line change
@@ -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 });
}
}
Loading
Loading