Skip to content

Commit 400a173

Browse files
authored
[PM-6104] Add locking support to bitwarden-json to improve bindings thread safety (#591)
## Type of change ``` - [ ] Bug fix - [ ] New feature development - [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ``` ## Objective I've added the lock into bitwarden-json because we already do locking in bitwarden-wasm and this way we can remove it from there.
1 parent 1595306 commit 400a173

File tree

8 files changed

+37
-38
lines changed

8 files changed

+37
-38
lines changed

Diff for: Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: crates/bitwarden-c/src/c.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{box_ptr, ffi_ref};
88
#[tokio::main]
99
pub async extern "C" fn run_command(
1010
c_str_ptr: *const c_char,
11-
client_ptr: *mut Client,
11+
client_ptr: *const Client,
1212
) -> *mut c_char {
1313
let client = unsafe { ffi_ref!(client_ptr) };
1414
let input_str = str::from_utf8(unsafe { CStr::from_ptr(c_str_ptr).to_bytes() }).unwrap();

Diff for: crates/bitwarden-c/src/macros/ffi.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
macro_rules! ffi_ref {
44
($name:ident) => {{
55
assert!(!$name.is_null());
6-
&mut *$name
6+
&*$name
77
}};
88
}
99

Diff for: crates/bitwarden-json/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal = ["bitwarden/internal"] # Internal testing methods
1818
secrets = ["bitwarden/secrets"] # Secrets manager API
1919

2020
[dependencies]
21+
async-lock = ">=3.3.0, <4.0"
2122
log = ">=0.4.18, <0.5"
2223
schemars = ">=0.8.12, <0.9"
2324
serde = { version = ">=1.0, <2.0", features = ["derive"] }

Diff for: crates/bitwarden-json/src/client.rs

+23-20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use async_lock::Mutex;
12
use bitwarden::client::client_settings::ClientSettings;
23

34
#[cfg(feature = "secrets")]
@@ -7,15 +8,15 @@ use crate::{
78
response::{Response, ResponseIntoString},
89
};
910

10-
pub struct Client(bitwarden::Client);
11+
pub struct Client(Mutex<bitwarden::Client>);
1112

1213
impl Client {
1314
pub fn new(settings_input: Option<String>) -> Self {
1415
let settings = Self::parse_settings(settings_input);
15-
Self(bitwarden::Client::new(settings))
16+
Self(Mutex::new(bitwarden::Client::new(settings)))
1617
}
1718

18-
pub async fn run_command(&mut self, input_str: &str) -> String {
19+
pub async fn run_command(&self, input_str: &str) -> String {
1920
const SUBCOMMANDS_TO_CLEAN: &[&str] = &["Secrets"];
2021
let mut cmd_value: serde_json::Value = match serde_json::from_str(input_str) {
2122
Ok(cmd) => cmd,
@@ -44,41 +45,43 @@ impl Client {
4445
}
4546
};
4647

48+
let mut client = self.0.lock().await;
49+
4750
match cmd {
4851
#[cfg(feature = "internal")]
49-
Command::PasswordLogin(req) => self.0.auth().login_password(&req).await.into_string(),
52+
Command::PasswordLogin(req) => client.auth().login_password(&req).await.into_string(),
5053
#[cfg(feature = "secrets")]
5154
Command::AccessTokenLogin(req) => {
52-
self.0.auth().login_access_token(&req).await.into_string()
55+
client.auth().login_access_token(&req).await.into_string()
5356
}
5457
#[cfg(feature = "internal")]
55-
Command::GetUserApiKey(req) => self.0.get_user_api_key(&req).await.into_string(),
58+
Command::GetUserApiKey(req) => client.get_user_api_key(&req).await.into_string(),
5659
#[cfg(feature = "internal")]
57-
Command::ApiKeyLogin(req) => self.0.auth().login_api_key(&req).await.into_string(),
60+
Command::ApiKeyLogin(req) => client.auth().login_api_key(&req).await.into_string(),
5861
#[cfg(feature = "internal")]
59-
Command::Sync(req) => self.0.sync(&req).await.into_string(),
62+
Command::Sync(req) => client.sync(&req).await.into_string(),
6063
#[cfg(feature = "internal")]
61-
Command::Fingerprint(req) => self.0.platform().fingerprint(&req).into_string(),
64+
Command::Fingerprint(req) => client.platform().fingerprint(&req).into_string(),
6265

6366
#[cfg(feature = "secrets")]
6467
Command::Secrets(cmd) => match cmd {
65-
SecretsCommand::Get(req) => self.0.secrets().get(&req).await.into_string(),
68+
SecretsCommand::Get(req) => client.secrets().get(&req).await.into_string(),
6669
SecretsCommand::GetByIds(req) => {
67-
self.0.secrets().get_by_ids(req).await.into_string()
70+
client.secrets().get_by_ids(req).await.into_string()
6871
}
69-
SecretsCommand::Create(req) => self.0.secrets().create(&req).await.into_string(),
70-
SecretsCommand::List(req) => self.0.secrets().list(&req).await.into_string(),
71-
SecretsCommand::Update(req) => self.0.secrets().update(&req).await.into_string(),
72-
SecretsCommand::Delete(req) => self.0.secrets().delete(req).await.into_string(),
72+
SecretsCommand::Create(req) => client.secrets().create(&req).await.into_string(),
73+
SecretsCommand::List(req) => client.secrets().list(&req).await.into_string(),
74+
SecretsCommand::Update(req) => client.secrets().update(&req).await.into_string(),
75+
SecretsCommand::Delete(req) => client.secrets().delete(req).await.into_string(),
7376
},
7477

7578
#[cfg(feature = "secrets")]
7679
Command::Projects(cmd) => match cmd {
77-
ProjectsCommand::Get(req) => self.0.projects().get(&req).await.into_string(),
78-
ProjectsCommand::Create(req) => self.0.projects().create(&req).await.into_string(),
79-
ProjectsCommand::List(req) => self.0.projects().list(&req).await.into_string(),
80-
ProjectsCommand::Update(req) => self.0.projects().update(&req).await.into_string(),
81-
ProjectsCommand::Delete(req) => self.0.projects().delete(req).await.into_string(),
80+
ProjectsCommand::Get(req) => client.projects().get(&req).await.into_string(),
81+
ProjectsCommand::Create(req) => client.projects().create(&req).await.into_string(),
82+
ProjectsCommand::List(req) => client.projects().list(&req).await.into_string(),
83+
ProjectsCommand::Update(req) => client.projects().update(&req).await.into_string(),
84+
ProjectsCommand::Delete(req) => client.projects().delete(req).await.into_string(),
8285
},
8386
}
8487
}

Diff for: crates/bitwarden-napi/src/client.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl BitwardenClient {
3838
}
3939

4040
#[napi]
41-
pub async unsafe fn run_command(&mut self, command_input: String) -> String {
41+
pub async fn run_command(&self, command_input: String) -> String {
4242
self.0.run_command(&command_input).await
4343
}
4444
}

Diff for: crates/bitwarden-py/src/client.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ impl BitwardenClient {
1313
}
1414

1515
#[pyo3(text_signature = "($self, command_input)")]
16-
fn run_command(&mut self, command_input: String) -> String {
17-
run_command(&mut self.0, &command_input)
16+
fn run_command(&self, command_input: String) -> String {
17+
run_command(&self.0, &command_input)
1818
}
1919
}
2020

2121
#[tokio::main]
22-
async fn run_command(client: &mut JsonClient, input_str: &str) -> String {
22+
async fn run_command(client: &JsonClient, input_str: &str) -> String {
2323
client.run_command(input_str).await
2424
}

Diff for: crates/bitwarden-wasm/src/client.rs

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
extern crate console_error_panic_hook;
2-
use std::{rc::Rc, sync::RwLock};
2+
use std::rc::Rc;
33

44
use bitwarden_json::client::Client as JsonClient;
55
use js_sys::Promise;
@@ -26,10 +26,10 @@ fn convert_level(level: LogLevel) -> Level {
2626
}
2727
}
2828

29-
// Rc<RwLock<...>> is to avoid needing to take ownership of the Client during our async run_command
29+
// Rc<...> is to avoid needing to take ownership of the Client during our async run_command
3030
// function https://github.com/rustwasm/wasm-bindgen/issues/2195#issuecomment-799588401
3131
#[wasm_bindgen]
32-
pub struct BitwardenClient(Rc<RwLock<JsonClient>>);
32+
pub struct BitwardenClient(Rc<JsonClient>);
3333

3434
#[wasm_bindgen]
3535
impl BitwardenClient {
@@ -42,20 +42,14 @@ impl BitwardenClient {
4242
panic!("failed to initialize logger: {:?}", e);
4343
}
4444

45-
Self(Rc::new(RwLock::new(bitwarden_json::client::Client::new(
46-
settings_input,
47-
))))
45+
Self(Rc::new(bitwarden_json::client::Client::new(settings_input)))
4846
}
4947

5048
#[wasm_bindgen]
51-
pub fn run_command(&mut self, js_input: String) -> Promise {
49+
pub fn run_command(&self, js_input: String) -> Promise {
5250
let rc = self.0.clone();
53-
// TODO: We should probably switch to an async-aware RwLock here,
54-
// but it probably doesn't matter much in a single threaded environment
55-
#[allow(clippy::await_holding_lock)]
5651
future_to_promise(async move {
57-
let mut client = rc.write().unwrap();
58-
let result = client.run_command(&js_input).await;
52+
let result = rc.run_command(&js_input).await;
5953
Ok(result.into())
6054
})
6155
}

0 commit comments

Comments
 (0)