,
+ },
}
#[derive(Subcommand, Clone)]
@@ -163,6 +168,12 @@ async fn process_commands() -> Result<()> {
client_id,
client_secret,
} => auth::login_api_key(client, client_id, client_secret).await?,
+ LoginCommands::Device {
+ email,
+ device_identifier,
+ } => {
+ auth::login_device(client, email, device_identifier).await?;
+ }
}
return Ok(());
}
diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md
index f85222738..d69e134c4 100644
--- a/languages/kotlin/doc.md
+++ b/languages/kotlin/doc.md
@@ -1280,9 +1280,9 @@ implementations.
Private Key generated by the `crate::auth::new_auth_request`. |
- protected_user_key |
+ method |
+ |
|
- User Key protected by the private key provided in `AuthRequestResponse`. |
From 6749058b7a2f196acff18d97eaae6c92a166fe8c Mon Sep 17 00:00:00 2001
From: Oscar Hinton
Date: Thu, 1 Feb 2024 16:47:59 +0100
Subject: [PATCH 18/34] Write more tests for Crypto crate (#570)
Extend unit testing for the crypto crate.
---
.../src/enc_string/asymmetric.rs | 33 ++++++++
crates/bitwarden-crypto/src/enc_string/mod.rs | 53 +++++++++++++
.../src/enc_string/symmetric.rs | 76 +++++++++++++++++++
3 files changed, 162 insertions(+)
diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs
index 04768db9e..f9bda838a 100644
--- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs
+++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs
@@ -206,6 +206,8 @@ impl schemars::JsonSchema for AsymmetricEncString {
#[cfg(test)]
mod tests {
+ use schemars::schema_for;
+
use super::{AsymmetricCryptoKey, AsymmetricEncString, KeyDecryptable};
const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY-----
@@ -288,4 +290,35 @@ XKZBokBGnjFnTnKcs7nv/O8=
assert_eq!(t.key.to_string(), cipher);
assert_eq!(serde_json::to_string(&t).unwrap(), serialized);
}
+
+ #[test]
+ fn test_from_str_invalid() {
+ let enc_str = "7.ABC";
+ let enc_string: Result = enc_str.parse();
+
+ let err = enc_string.unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "EncString error, Invalid asymmetric type, got type 7 with 1 parts"
+ );
+ }
+
+ #[test]
+ fn test_debug_format() {
+ let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw==";
+ let enc_string: AsymmetricEncString = enc_str.parse().unwrap();
+
+ let debug_string = format!("{:?}", enc_string);
+ assert_eq!(debug_string, "AsymmetricEncString");
+ }
+
+ #[test]
+ fn test_json_schema() {
+ let schema = schema_for!(AsymmetricEncString);
+
+ assert_eq!(
+ serde_json::to_string(&schema).unwrap(),
+ r#"{"$schema":"http://json-schema.org/draft-07/schema#","title":"AsymmetricEncString","type":"string"}"#
+ );
+ }
}
diff --git a/crates/bitwarden-crypto/src/enc_string/mod.rs b/crates/bitwarden-crypto/src/enc_string/mod.rs
index e1433821f..3250c1a58 100644
--- a/crates/bitwarden-crypto/src/enc_string/mod.rs
+++ b/crates/bitwarden-crypto/src/enc_string/mod.rs
@@ -75,3 +75,56 @@ where
T::from_str(v).map_err(|e| E::custom(format!("{:?}", e)))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_check_length_less_than_expected() {
+ let buf = [1, 2, 3];
+ let expected = 5;
+ let result = check_length(&buf, expected);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_check_length_equal_to_expected() {
+ let buf = [1, 2, 3, 4, 5];
+ let expected = 5;
+ let result = check_length(&buf, expected);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_check_length_greater_than_expected() {
+ let buf = [1, 2, 3, 4, 5, 6];
+ let expected = 5;
+ let result = check_length(&buf, expected);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_split_enc_string_new_format() {
+ let s = "2.abc|def|ghi";
+ let (header, parts) = split_enc_string(s);
+ assert_eq!(header, "2");
+ assert_eq!(parts, vec!["abc", "def", "ghi"]);
+ }
+
+ #[test]
+ fn test_split_enc_string_old_format_three_parts() {
+ let s = "abc|def|ghi";
+ let (header, parts) = split_enc_string(s);
+ assert_eq!(header, "1");
+ assert_eq!(parts, vec!["abc", "def", "ghi"]);
+ }
+
+ #[test]
+ fn test_split_enc_string_old_format_fewer_parts() {
+ let s = "abc|def";
+ let (header, parts) = split_enc_string(s);
+ assert_eq!(header, "0");
+ assert_eq!(parts, vec!["abc", "def"]);
+ }
+}
diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs
index a76315e96..a7768de23 100644
--- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs
+++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs
@@ -274,6 +274,8 @@ impl schemars::JsonSchema for EncString {
#[cfg(test)]
mod tests {
+ use schemars::schema_for;
+
use super::EncString;
use crate::{derive_symmetric_key, KeyDecryptable, KeyEncryptable};
@@ -325,4 +327,78 @@ mod tests {
assert_eq!(enc_string_new.to_string(), enc_str)
}
+
+ #[test]
+ fn test_from_str_cbc256() {
+ let enc_str = "0.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==";
+ let enc_string: EncString = enc_str.parse().unwrap();
+
+ assert_eq!(enc_string.enc_type(), 0);
+ if let EncString::AesCbc256_B64 { iv, data } = &enc_string {
+ assert_eq!(
+ iv,
+ &[164, 196, 186, 254, 39, 19, 64, 0, 109, 186, 92, 57, 218, 154, 182, 150]
+ );
+ assert_eq!(
+ data,
+ &[93, 118, 241, 43, 16, 211, 135, 233, 150, 136, 221, 71, 140, 125, 141, 215]
+ );
+ }
+ }
+
+ #[test]
+ fn test_from_str_cbc128_hmac() {
+ let enc_str = "1.Hh8gISIjJCUmJygpKissLQ==|MjM0NTY3ODk6Ozw9Pj9AQUJDREU=|KCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkc=";
+ let enc_string: EncString = enc_str.parse().unwrap();
+
+ assert_eq!(enc_string.enc_type(), 1);
+ if let EncString::AesCbc128_HmacSha256_B64 { iv, mac, data } = &enc_string {
+ assert_eq!(
+ iv,
+ &[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45]
+ );
+ assert_eq!(
+ mac,
+ &[
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71
+ ]
+ );
+ assert_eq!(
+ data,
+ &[50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
+ );
+ }
+ }
+
+ #[test]
+ fn test_from_str_invalid() {
+ let enc_str = "7.ABC";
+ let enc_string: Result = enc_str.parse();
+
+ let err = enc_string.unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "EncString error, Invalid symmetric type, got type 7 with 1 parts"
+ );
+ }
+
+ #[test]
+ fn test_debug_format() {
+ let enc_str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
+ let enc_string: EncString = enc_str.parse().unwrap();
+
+ let debug_string = format!("{:?}", enc_string);
+ assert_eq!(debug_string, "EncString");
+ }
+
+ #[test]
+ fn test_json_schema() {
+ let schema = schema_for!(EncString);
+
+ assert_eq!(
+ serde_json::to_string(&schema).unwrap(),
+ r#"{"$schema":"http://json-schema.org/draft-07/schema#","title":"EncString","type":"string"}"#
+ );
+ }
}
From ac58ac9becb16fbf28862a02783fc4140a1c04f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Garc=C3=ADa?=
Date: Fri, 2 Feb 2024 12:06:04 +0100
Subject: [PATCH 19/34] Add test to ensure we are using a compatible rustls
version (#569)
## Type of change
```
- [x] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
```
## Objective
We need to keep the version of rustls that reqwest and
rustls-platform-verifier use in sync to avoid runtime errors when
creating the reqwest client, we can't check it at compile time due to
the use of `&dyn Any` and downcasting, but we can add a test.
---
crates/bitwarden/src/client/client.rs | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs
index b8632e21e..5b2c8b7e0 100644
--- a/crates/bitwarden/src/client/client.rs
+++ b/crates/bitwarden/src/client/client.rs
@@ -306,3 +306,23 @@ impl Client {
Ok(self.encryption_settings.as_ref().unwrap())
}
}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_reqwest_rustls_platform_verifier_are_compatible() {
+ // rustls-platform-verifier is generating a rustls::ClientConfig,
+ // which reqwest accepts as a &dyn Any and then downcasts it to a
+ // rustls::ClientConfig.
+
+ // This means that if the rustls version of the two crates don't match,
+ // the downcast will fail and we will get a runtime error.
+
+ // This tests is added to ensure that it doesn't happen.
+
+ let _ = reqwest::ClientBuilder::new()
+ .use_preconfigured_tls(rustls_platform_verifier::tls_config())
+ .build()
+ .unwrap();
+ }
+}
From 32be569f753794884930bbc0e102313a7cc04487 Mon Sep 17 00:00:00 2001
From: Oscar Hinton
Date: Fri, 2 Feb 2024 13:36:41 +0100
Subject: [PATCH 20/34] Support validating password by decrypting user key
(#572)
Enables password validation without knowing the password hash by
decrypting the user key.
---
crates/bitwarden-uniffi/src/auth/mod.rs | 23 +++-
crates/bitwarden/src/auth/client_auth.rs | 15 ++-
crates/bitwarden/src/auth/login/password.rs | 3 +-
crates/bitwarden/src/auth/login/two_factor.rs | 3 +-
crates/bitwarden/src/auth/mod.rs | 10 +-
crates/bitwarden/src/auth/password/mod.rs | 2 +
.../bitwarden/src/auth/password/validate.rs | 121 ++++++++++++++++--
7 files changed, 153 insertions(+), 24 deletions(-)
diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs
index 62c791967..75e0c5656 100644
--- a/crates/bitwarden-uniffi/src/auth/mod.rs
+++ b/crates/bitwarden-uniffi/src/auth/mod.rs
@@ -90,8 +90,27 @@ impl ClientAuth {
.write()
.await
.auth()
- .validate_password(password, password_hash.to_string())
- .await?)
+ .validate_password(password, password_hash.to_string())?)
+ }
+
+ /// Validate the user password without knowing the password hash
+ ///
+ /// Used for accounts that we know have master passwords but that have not logged in with a
+ /// password. Some example are login with device or TDE.
+ ///
+ /// This works by comparing the provided password against the encrypted user key.
+ pub async fn validate_password_user_key(
+ &self,
+ password: String,
+ encrypted_user_key: String,
+ ) -> Result {
+ Ok(self
+ .0
+ .0
+ .write()
+ .await
+ .auth()
+ .validate_password_user_key(password, encrypted_user_key)?)
}
/// Initialize a new auth request
diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs
index 0ae2f6f33..f08daf0b7 100644
--- a/crates/bitwarden/src/auth/client_auth.rs
+++ b/crates/bitwarden/src/auth/client_auth.rs
@@ -16,7 +16,8 @@ use crate::{
TwoFactorEmailRequest,
},
password::{
- password_strength, satisfies_policy, validate_password, MasterPasswordPolicyOptions,
+ password_strength, satisfies_policy, validate_password, validate_password_user_key,
+ MasterPasswordPolicyOptions,
},
register::{make_register_keys, register},
AuthRequestResponse, RegisterKeyResponse, RegisterRequest,
@@ -101,8 +102,16 @@ impl<'a> ClientAuth<'a> {
send_two_factor_email(self.client, tf).await
}
- pub async fn validate_password(&self, password: String, password_hash: String) -> Result {
- validate_password(self.client, password, password_hash).await
+ pub fn validate_password(&self, password: String, password_hash: String) -> Result {
+ validate_password(self.client, password, password_hash)
+ }
+
+ pub fn validate_password_user_key(
+ &self,
+ password: String,
+ encrypted_user_key: String,
+ ) -> Result {
+ validate_password_user_key(self.client, password, encrypted_user_key)
}
pub fn new_auth_request(&self, email: &str) -> Result {
diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs
index 4c978e559..02552b70e 100644
--- a/crates/bitwarden/src/auth/login/password.rs
+++ b/crates/bitwarden/src/auth/login/password.rs
@@ -34,8 +34,7 @@ pub(crate) async fn login_password(
&input.kdf,
&input.password,
HashPurpose::ServerAuthorization,
- )
- .await?;
+ )?;
let response = request_identity_tokens(client, input, &password_hash).await?;
if let IdentityTokenResponse::Authenticated(r) = &response {
diff --git a/crates/bitwarden/src/auth/login/two_factor.rs b/crates/bitwarden/src/auth/login/two_factor.rs
index 45be042c7..c8f0cc55b 100644
--- a/crates/bitwarden/src/auth/login/two_factor.rs
+++ b/crates/bitwarden/src/auth/login/two_factor.rs
@@ -27,8 +27,7 @@ pub(crate) async fn send_two_factor_email(
&kdf,
&input.password,
HashPurpose::ServerAuthorization,
- )
- .await?;
+ )?;
let config = client.get_api_configurations().await;
bitwarden_api_api::apis::two_factor_api::two_factor_send_email_login_post(
diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden/src/auth/mod.rs
index d77847574..021c97c0f 100644
--- a/crates/bitwarden/src/auth/mod.rs
+++ b/crates/bitwarden/src/auth/mod.rs
@@ -23,7 +23,7 @@ pub(crate) use auth_request::{auth_request_decrypt_master_key, auth_request_decr
use crate::{client::Kdf, error::Result};
#[cfg(feature = "internal")]
-async fn determine_password_hash(
+fn determine_password_hash(
email: &str,
kdf: &Kdf,
password: &str,
@@ -40,8 +40,8 @@ mod tests {
use super::*;
#[cfg(feature = "internal")]
- #[tokio::test]
- async fn test_determine_password_hash() {
+ #[test]
+ fn test_determine_password_hash() {
use super::determine_password_hash;
let password = "password123";
@@ -51,9 +51,7 @@ mod tests {
};
let purpose = HashPurpose::LocalAuthorization;
- let result = determine_password_hash(email, &kdf, password, purpose)
- .await
- .unwrap();
+ let result = determine_password_hash(email, &kdf, password, purpose).unwrap();
assert_eq!(result, "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU=");
}
diff --git a/crates/bitwarden/src/auth/password/mod.rs b/crates/bitwarden/src/auth/password/mod.rs
index b7833c7f8..d0f3329f2 100644
--- a/crates/bitwarden/src/auth/password/mod.rs
+++ b/crates/bitwarden/src/auth/password/mod.rs
@@ -3,5 +3,7 @@ pub(crate) use policy::satisfies_policy;
pub use policy::MasterPasswordPolicyOptions;
mod validate;
pub(crate) use validate::validate_password;
+#[cfg(feature = "internal")]
+pub(crate) use validate::validate_password_user_key;
mod strength;
pub(crate) use strength::password_strength;
diff --git a/crates/bitwarden/src/auth/password/validate.rs b/crates/bitwarden/src/auth/password/validate.rs
index f6d22e11a..9003347d9 100644
--- a/crates/bitwarden/src/auth/password/validate.rs
+++ b/crates/bitwarden/src/auth/password/validate.rs
@@ -1,4 +1,4 @@
-use bitwarden_crypto::HashPurpose;
+use bitwarden_crypto::{HashPurpose, MasterKey};
use crate::{
auth::determine_password_hash,
@@ -8,7 +8,7 @@ use crate::{
};
/// Validate if the provided password matches the password hash stored in the client.
-pub(crate) async fn validate_password(
+pub(crate) fn validate_password(
client: &Client,
password: String,
password_hash: String,
@@ -22,9 +22,12 @@ pub(crate) async fn validate_password(
match login_method {
UserLoginMethod::Username { email, kdf, .. }
| UserLoginMethod::ApiKey { email, kdf, .. } => {
- let hash =
- determine_password_hash(email, kdf, &password, HashPurpose::LocalAuthorization)
- .await?;
+ let hash = determine_password_hash(
+ email,
+ kdf,
+ &password,
+ HashPurpose::LocalAuthorization,
+ )?;
Ok(hash == password_hash)
}
@@ -34,13 +37,53 @@ pub(crate) async fn validate_password(
}
}
+#[cfg(feature = "internal")]
+pub(crate) fn validate_password_user_key(
+ client: &Client,
+ password: String,
+ encrypted_user_key: String,
+) -> Result {
+ let login_method = client
+ .login_method
+ .as_ref()
+ .ok_or(Error::NotAuthenticated)?;
+
+ if let LoginMethod::User(login_method) = login_method {
+ match login_method {
+ UserLoginMethod::Username { email, kdf, .. }
+ | UserLoginMethod::ApiKey { email, kdf, .. } => {
+ let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?;
+ let user_key = master_key
+ .decrypt_user_key(encrypted_user_key.parse()?)
+ .map_err(|_| "wrong password")?;
+
+ let enc = client
+ .get_encryption_settings()
+ .map_err(|_| Error::VaultLocked)?;
+
+ let existing_key = enc.get_key(&None).ok_or(Error::VaultLocked)?;
+
+ if user_key.to_vec() != existing_key.to_vec() {
+ return Err("wrong user key".into());
+ }
+
+ Ok(master_key
+ .derive_master_key_hash(password.as_bytes(), HashPurpose::LocalAuthorization)?)
+ }
+ }
+ } else {
+ Err(Error::NotAuthenticated)
+ }
+}
+
#[cfg(test)]
mod tests {
- #[tokio::test]
- async fn test_validate_password() {
+ use crate::auth::password::{validate::validate_password_user_key, validate_password};
+
+ #[test]
+ fn test_validate_password() {
use std::num::NonZeroU32;
- use super::validate_password;
use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod};
let mut client = Client::new(None);
@@ -55,8 +98,68 @@ mod tests {
let password = "password123".to_string();
let password_hash = "7kTqkF1pY/3JeOu73N9kR99fDDe9O1JOZaVc7KH3lsU=".to_string();
- let result = validate_password(&client, password, password_hash).await;
+ let result = validate_password(&client, password, password_hash);
assert!(result.unwrap());
}
+
+ #[cfg(feature = "internal")]
+ #[test]
+ fn test_validate_password_user_key() {
+ use std::num::NonZeroU32;
+
+ use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod};
+
+ let mut client = Client::new(None);
+ client.set_login_method(LoginMethod::User(UserLoginMethod::Username {
+ email: "test@bitwarden.com".to_string(),
+ kdf: Kdf::PBKDF2 {
+ iterations: NonZeroU32::new(600_000).unwrap(),
+ },
+ client_id: "1".to_string(),
+ }));
+
+ let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
+ let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap();
+
+ client
+ .initialize_user_crypto("asdfasdfasdf", user_key.parse().unwrap(), private_key)
+ .unwrap();
+
+ let result =
+ validate_password_user_key(&client, "asdfasdfasdf".to_string(), user_key.to_string())
+ .unwrap();
+
+ assert_eq!(result, "aOvkBXFhSdgrBWR3hZCMRoML9+h5yRblU3lFphCdkeA=");
+ assert!(validate_password(&client, "asdfasdfasdf".to_string(), result.to_string()).unwrap())
+ }
+
+ #[cfg(feature = "internal")]
+ #[test]
+ fn test_validate_password_user_key_wrong_password() {
+ use std::num::NonZeroU32;
+
+ use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod};
+
+ let mut client = Client::new(None);
+ client.set_login_method(LoginMethod::User(UserLoginMethod::Username {
+ email: "test@bitwarden.com".to_string(),
+ kdf: Kdf::PBKDF2 {
+ iterations: NonZeroU32::new(600_000).unwrap(),
+ },
+ client_id: "1".to_string(),
+ }));
+
+ let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
+ let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap();
+
+ client
+ .initialize_user_crypto("asdfasdfasdf", user_key.parse().unwrap(), private_key)
+ .unwrap();
+
+ let result = validate_password_user_key(&client, "abc".to_string(), user_key.to_string())
+ .unwrap_err();
+
+ assert_eq!(result.to_string(), "Internal error: wrong password");
+ }
}
From 0be34620bfa94def4d09cab515ca287b9a6ed7ce Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 10:41:05 +0100
Subject: [PATCH 21/34] [deps]: Update Rust crate toml to 0.8.9 (#577)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [toml](https://togithub.com/toml-rs/toml) | dependencies | patch |
`0.8.8` -> `0.8.9` |
---
### Release Notes
toml-rs/toml (toml)
###
[`v0.8.9`](https://togithub.com/toml-rs/toml/compare/toml-v0.8.8...toml-v0.8.9)
[Compare
Source](https://togithub.com/toml-rs/toml/compare/toml-v0.8.8...toml-v0.8.9)
---
### Configuration
📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At
any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
Cargo.lock | 10 +++++-----
crates/bws/Cargo.toml | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 8ebf52136..f7e721c1a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -612,7 +612,7 @@ dependencies = [
"tempfile",
"thiserror",
"tokio",
- "toml 0.8.8",
+ "toml 0.8.9",
"uuid",
]
@@ -3452,9 +3452,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.8.8"
+version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
+checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325"
dependencies = [
"serde",
"serde_spanned",
@@ -3473,9 +3473,9 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.21.0"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
+checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [
"indexmap 2.2.1",
"serde",
diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml
index 1c04a6668..bb757eca6 100644
--- a/crates/bws/Cargo.toml
+++ b/crates/bws/Cargo.toml
@@ -37,7 +37,7 @@ serde_yaml = "0.9"
supports-color = "2.1.0"
thiserror = "1.0.56"
tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] }
-toml = "0.8.8"
+toml = "0.8.9"
uuid = { version = "^1.7.0", features = ["serde"] }
bitwarden = { path = "../bitwarden", version = "0.4.0", features = ["secrets"] }
From ddd98880cf6ee026ab38cb0446827ef5351e89b5 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 11:01:34 +0100
Subject: [PATCH 22/34] [deps]: Update Rust crate itertools to 0.12.1 (#576)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [itertools](https://togithub.com/rust-itertools/itertools) |
dependencies | patch | `0.12.0` -> `0.12.1` |
---
### Release Notes
rust-itertools/itertools (itertools)
###
[`v0.12.1`](https://togithub.com/rust-itertools/itertools/blob/HEAD/CHANGELOG.md#0121)
[Compare
Source](https://togithub.com/rust-itertools/itertools/compare/v0.12.0...v0.12.1)
##### Added
- Documented iteration order guarantee for
`Itertools::[tuple_]combinations`
([#822](https://togithub.com/rust-itertools/itertools/issues/822))
- Documented possible panic in `iterate`
([#842](https://togithub.com/rust-itertools/itertools/issues/842))
- Implemented `Clone` and `Debug` for `Diff`
([#845](https://togithub.com/rust-itertools/itertools/issues/845))
- Implemented `Debug` for `WithPosition`
([#859](https://togithub.com/rust-itertools/itertools/issues/859))
- Implemented `Eq` for `MinMaxResult`
([#838](https://togithub.com/rust-itertools/itertools/issues/838))
- Implemented `From>` for `Option>`
([#843](https://togithub.com/rust-itertools/itertools/issues/843))
- Implemented `PeekingNext` for `RepeatN`
([#855](https://togithub.com/rust-itertools/itertools/issues/855))
##### Changed
- Made `CoalesceBy` lazy
([#801](https://togithub.com/rust-itertools/itertools/issues/801))
- Optimized `Filter[Map]Ok::next`, `Itertools::partition`,
`Unique[By]::next[_back]`
([#818](https://togithub.com/rust-itertools/itertools/issues/818))
- Optimized `Itertools::find_position`
([#837](https://togithub.com/rust-itertools/itertools/issues/837))
- Optimized `Positions::next[_back]`
([#816](https://togithub.com/rust-itertools/itertools/issues/816))
- Optimized `ZipLongest::fold`
([#854](https://togithub.com/rust-itertools/itertools/issues/854))
- Relaxed `Debug` bounds for `GroupingMapBy`
([#860](https://togithub.com/rust-itertools/itertools/issues/860))
- Specialized `ExactlyOneError::fold`
([#826](https://togithub.com/rust-itertools/itertools/issues/826))
- Specialized `Interleave[Shortest]::fold`
([#849](https://togithub.com/rust-itertools/itertools/issues/849))
- Specialized `MultiPeek::fold`
([#820](https://togithub.com/rust-itertools/itertools/issues/820))
- Specialized `PadUsing::[r]fold`
([#825](https://togithub.com/rust-itertools/itertools/issues/825))
- Specialized `PeekNth::fold`
([#824](https://togithub.com/rust-itertools/itertools/issues/824))
- Specialized `Positions::[r]fold`
([#813](https://togithub.com/rust-itertools/itertools/issues/813))
- Specialized `PutBackN::fold`
([#823](https://togithub.com/rust-itertools/itertools/issues/823))
- Specialized `RepeatN::[r]fold`
([#821](https://togithub.com/rust-itertools/itertools/issues/821))
- Specialized `TakeWhileInclusive::fold`
([#851](https://togithub.com/rust-itertools/itertools/issues/851))
- Specialized `ZipLongest::rfold`
([#848](https://togithub.com/rust-itertools/itertools/issues/848))
##### Notable Internal Changes
- Added test coverage in CI
([#847](https://togithub.com/rust-itertools/itertools/issues/847),
[#856](https://togithub.com/rust-itertools/itertools/issues/856))
- Added semver check in CI
([#784](https://togithub.com/rust-itertools/itertools/issues/784))
- Enforced `clippy` in CI
([#740](https://togithub.com/rust-itertools/itertools/issues/740))
- Enforced `rustdoc` in CI
([#840](https://togithub.com/rust-itertools/itertools/issues/840))
- Improved specialization tests
([#807](https://togithub.com/rust-itertools/itertools/issues/807))
- More specialization benchmarks
([#806](https://togithub.com/rust-itertools/itertools/issues/806))
---
### Configuration
📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At
any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
Cargo.lock | 6 +++---
crates/sdk-schemas/Cargo.toml | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index f7e721c1a..03f0cfc2f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1806,9 +1806,9 @@ dependencies = [
[[package]]
name = "itertools"
-version = "0.12.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
@@ -2893,7 +2893,7 @@ dependencies = [
"bitwarden",
"bitwarden-json",
"bitwarden-uniffi",
- "itertools 0.12.0",
+ "itertools 0.12.1",
"schemars",
"serde_json",
]
diff --git a/crates/sdk-schemas/Cargo.toml b/crates/sdk-schemas/Cargo.toml
index 3e7282e7e..81a9d76ca 100644
--- a/crates/sdk-schemas/Cargo.toml
+++ b/crates/sdk-schemas/Cargo.toml
@@ -13,7 +13,7 @@ internal = [
[dependencies]
anyhow = "1.0.79"
-itertools = "0.12.0"
+itertools = "0.12.1"
schemars = { version = "0.8.16", features = ["preserve_order"] }
serde_json = "1.0.113"
From 098870d2b02d1a3abee461c0e9e318a0ecd829dc Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 11:02:11 +0100
Subject: [PATCH 23/34] [deps]: Update Rust crate tokio to 1.36.0 (#579)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [tokio](https://tokio.rs)
([source](https://togithub.com/tokio-rs/tokio)) | dependencies | minor |
`1.35.1` -> `1.36.0` |
| [tokio](https://tokio.rs)
([source](https://togithub.com/tokio-rs/tokio)) | dev-dependencies |
minor | `1.35.1` -> `1.36.0` |
---
### Release Notes
tokio-rs/tokio (tokio)
###
[`v1.36.0`](https://togithub.com/tokio-rs/tokio/releases/tag/tokio-1.36.0):
Tokio v1.36.0
[Compare
Source](https://togithub.com/tokio-rs/tokio/compare/tokio-1.35.1...tokio-1.36.0)
##### 1.36.0 (February 2nd, 2024)
##### Added
- io: add `tokio::io::Join` ([#6220])
- io: implement `AsyncWrite` for `Empty` ([#6235])
- net: add support for anonymous unix pipes ([#6127])
- net: add `UnixSocket` ([#6290])
- net: expose keepalive option on `TcpSocket` ([#6311])
- sync: add `{Receiver,UnboundedReceiver}::poll_recv_many`
([#6236])
- sync: add `Sender::{try_,}reserve_many` ([#6205])
- sync: add `watch::Receiver::mark_unchanged` ([#6252])
- task: add `JoinSet::try_join_next` ([#6280])
- time: add `FutureExt::timeout` ([#6276])
##### Changed
- io: make `copy` cooperative ([#6265])
- io: make `repeat` and `sink` cooperative ([#6254])
- io: simplify check for empty slice ([#6293])
- rt: improve robustness of `wake_in_drop_after_panic` test
([#6238])
- process: use pidfd on Linux when available ([#6152])
- sync: use AtomicBool in broadcast channel future ([#6298])
##### Fixed
- chore: typographic improvements ([#6262])
- runtime: remove obsolete comment ([#6303])
- task: fix typo ([#6261])
##### Documented
- io: clarify `clear_ready` docs ([#6304])
- net: document that `*Fd` traits on `TcpSocket` are unix-only
([#6294])
- sync: document FIFO behavior of `tokio::sync::Mutex` ([#6279])
[#6220]: https://togithub.com/tokio-rs/tokio/pull/6220
[#6235]: https://togithub.com/tokio-rs/tokio/pull/6235
[#6127]: https://togithub.com/tokio-rs/tokio/pull/6127
[#6290]: https://togithub.com/tokio-rs/tokio/pull/6290
[#6311]: https://togithub.com/tokio-rs/tokio/pull/6311
[#6236]: https://togithub.com/tokio-rs/tokio/pull/6236
[#6205]: https://togithub.com/tokio-rs/tokio/pull/6205
[#6252]: https://togithub.com/tokio-rs/tokio/pull/6252
[#6280]: https://togithub.com/tokio-rs/tokio/pull/6280
[#6276]: https://togithub.com/tokio-rs/tokio/pull/6276
[#6265]: https://togithub.com/tokio-rs/tokio/pull/6265
[#6254]: https://togithub.com/tokio-rs/tokio/pull/6254
[#6293]: https://togithub.com/tokio-rs/tokio/pull/6293
[#6238]: https://togithub.com/tokio-rs/tokio/pull/6238
[#6152]: https://togithub.com/tokio-rs/tokio/pull/6152
[#6298]: https://togithub.com/tokio-rs/tokio/pull/6298
[#6262]: https://togithub.com/tokio-rs/tokio/pull/6262
[#6303]: https://togithub.com/tokio-rs/tokio/pull/6303
[#6261]: https://togithub.com/tokio-rs/tokio/pull/6261
[#6304]: https://togithub.com/tokio-rs/tokio/pull/6304
[#6294]: https://togithub.com/tokio-rs/tokio/pull/6294
[#6279]: https://togithub.com/tokio-rs/tokio/pull/6279
---
### Configuration
📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At
any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
Cargo.lock | 4 ++--
crates/bitwarden-generators/Cargo.toml | 2 +-
crates/bitwarden-py/Cargo.toml | 2 +-
crates/bitwarden/Cargo.toml | 2 +-
crates/bw/Cargo.toml | 2 +-
crates/bws/Cargo.toml | 2 +-
6 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 03f0cfc2f..b4469fc04 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3391,9 +3391,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.35.1"
+version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
+checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
diff --git a/crates/bitwarden-generators/Cargo.toml b/crates/bitwarden-generators/Cargo.toml
index 7f26a47cf..39eeb3d71 100644
--- a/crates/bitwarden-generators/Cargo.toml
+++ b/crates/bitwarden-generators/Cargo.toml
@@ -29,5 +29,5 @@ uniffi = { version = "=0.26.1", optional = true }
[dev-dependencies]
rand_chacha = "0.3.1"
-tokio = { version = "1.35.1", features = ["rt", "macros"] }
+tokio = { version = "1.36.0", features = ["rt", "macros"] }
wiremock = "0.5.22"
diff --git a/crates/bitwarden-py/Cargo.toml b/crates/bitwarden-py/Cargo.toml
index ed7c5df5e..e81f2f5a8 100644
--- a/crates/bitwarden-py/Cargo.toml
+++ b/crates/bitwarden-py/Cargo.toml
@@ -18,7 +18,7 @@ bitwarden-json = { path = "../bitwarden-json", features = ["secrets"] }
pyo3-build-config = { version = "0.20.2" }
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
-tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] }
+tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] }
pyo3-asyncio = { version = "0.20.0", features = [
"attributes",
"tokio-runtime",
diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml
index b52e2f8f3..d9508258b 100644
--- a/crates/bitwarden/Cargo.toml
+++ b/crates/bitwarden/Cargo.toml
@@ -76,6 +76,6 @@ reqwest = { version = "*", features = [
[dev-dependencies]
rand_chacha = "0.3.1"
-tokio = { version = "1.35.1", features = ["rt", "macros"] }
+tokio = { version = "1.36.0", features = ["rt", "macros"] }
wiremock = "0.5.22"
zeroize = { version = ">=1.7.0, <2.0", features = ["derive", "aarch64"] }
diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml
index cc07397cd..1943e5ad5 100644
--- a/crates/bw/Cargo.toml
+++ b/crates/bw/Cargo.toml
@@ -18,7 +18,7 @@ color-eyre = "0.6"
env_logger = "0.11.1"
inquire = "0.6.2"
log = "0.4.20"
-tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] }
+tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] }
bitwarden = { path = "../bitwarden", version = "0.4.0", features = [
"internal",
diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml
index bb757eca6..608db45a8 100644
--- a/crates/bws/Cargo.toml
+++ b/crates/bws/Cargo.toml
@@ -36,7 +36,7 @@ serde_json = "^1.0.113"
serde_yaml = "0.9"
supports-color = "2.1.0"
thiserror = "1.0.56"
-tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros"] }
+tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] }
toml = "0.8.9"
uuid = { version = "^1.7.0", features = ["serde"] }
From bf02c3ba5a6080c4d67765e01a7d6e85e49e1363 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 11:02:56 +0100
Subject: [PATCH 24/34] [deps]: Update Rust crate clap_complete to 4.4.10
(#575)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [clap_complete](https://togithub.com/clap-rs/clap)
([source](https://togithub.com/clap-rs/clap/tree/HEAD/clap_complete)) |
dependencies | patch | `4.4.9` -> `4.4.10` |
---
### Release Notes
clap-rs/clap (clap_complete)
###
[`v4.4.10`](https://togithub.com/clap-rs/clap/blob/HEAD/clap_complete/CHANGELOG.md#4410---2024-02-02)
[Compare
Source](https://togithub.com/clap-rs/clap/compare/clap_complete-v4.4.9...clap_complete-v4.4.10)
##### Fixes
- *(bash)* Allow completing filenames with spaces
---
### Configuration
📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At
any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
Cargo.lock | 4 ++--
crates/bws/Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index b4469fc04..6cae436a0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -752,9 +752,9 @@ dependencies = [
[[package]]
name = "clap_complete"
-version = "4.4.9"
+version = "4.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df631ae429f6613fcd3a7c1adbdb65f637271e561b03680adaa6573015dfb106"
+checksum = "abb745187d7f4d76267b37485a65e0149edd0e91a4cfcdd3f27524ad86cee9f3"
dependencies = [
"clap",
]
diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml
index 608db45a8..3766ad682 100644
--- a/crates/bws/Cargo.toml
+++ b/crates/bws/Cargo.toml
@@ -21,7 +21,7 @@ chrono = { version = "0.4.33", features = [
"std",
], default-features = false }
clap = { version = "4.4.18", features = ["derive", "env", "string"] }
-clap_complete = "4.4.9"
+clap_complete = "4.4.10"
color-eyre = "0.6"
comfy-table = "^7.1.0"
directories = "5.0.1"
From 4883eba21061b67fa5b821aa8e6926d976eeb621 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 11:41:26 +0100
Subject: [PATCH 25/34] [deps]: Update Rust crate supports-color to v3 (#582)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [supports-color](https://togithub.com/zkat/supports-color) |
dependencies | major | `2.1.0` -> `3.0.0` |
---
### Release Notes
zkat/supports-color (supports-color)
###
[`v3.0.0`](https://togithub.com/zkat/supports-color/blob/HEAD/CHANGELOG.md#300-2024-02-04)
[Compare
Source](https://togithub.com/zkat/supports-color/compare/v2.1.0...v3.0.0)
##### Features
- **deps:** Replace `is-terminal` with `std::io::IsTerminal`
([#11](https://togithub.com/zkat/supports-color/issues/11))
([6fb6e359](https://togithub.com/zkat/supports-color/commit/6fb6e35961055a701264d879744f615c25b7629d))
- **BREAKING CHANGE**: This bumps the MSRV to 1.70.0 due to the new
`std::io::IsTerminal` API.
---
### Configuration
📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At
any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
Cargo.lock | 20 ++++----------------
crates/bitwarden-cli/Cargo.toml | 2 +-
crates/bws/Cargo.toml | 2 +-
3 files changed, 6 insertions(+), 18 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 6cae436a0..9054b7734 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1778,22 +1778,11 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
-[[package]]
-name = "is-terminal"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
-dependencies = [
- "hermit-abi",
- "rustix",
- "windows-sys 0.52.0",
-]
-
[[package]]
name = "is_ci"
-version = "1.1.1"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
+checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "itertools"
@@ -3215,11 +3204,10 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "supports-color"
-version = "2.1.0"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
+checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f"
dependencies = [
- "is-terminal",
"is_ci",
]
diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml
index bded30904..4248c9189 100644
--- a/crates/bitwarden-cli/Cargo.toml
+++ b/crates/bitwarden-cli/Cargo.toml
@@ -8,4 +8,4 @@ rust-version = "1.57"
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6"
inquire = "0.6.2"
-supports-color = "2.1.0"
+supports-color = "3.0.0"
diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml
index 3766ad682..8acc9f92c 100644
--- a/crates/bws/Cargo.toml
+++ b/crates/bws/Cargo.toml
@@ -34,7 +34,7 @@ regex = { version = "1.10.3", features = [
serde = "^1.0.196"
serde_json = "^1.0.113"
serde_yaml = "0.9"
-supports-color = "2.1.0"
+supports-color = "3.0.0"
thiserror = "1.0.56"
tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] }
toml = "0.8.9"
From 851f4a6896580894b7e95a106d78fbe152879c9d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 11:41:46 +0100
Subject: [PATCH 26/34] [deps]: Update @types/node to v18.19.14 (#574)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/node](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`18.19.10` ->
`18.19.14`](https://renovatebot.com/diffs/npm/@types%2fnode/18.19.10/18.19.14)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/18.19.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/18.19.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/18.19.10/18.19.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/18.19.10/18.19.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
---
### Configuration
📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At
any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
languages/js/sdk-client/package-lock.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json
index d1192e6f5..eb8d010db 100644
--- a/languages/js/sdk-client/package-lock.json
+++ b/languages/js/sdk-client/package-lock.json
@@ -39,9 +39,9 @@
}
},
"node_modules/@types/node": {
- "version": "18.19.10",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.10.tgz",
- "integrity": "sha512-IZD8kAM02AW1HRDTPOlz3npFava678pr8Ie9Vp8uRhBROXAv8MXT2pCnGZZAKYdromsNQLHQcfWQ6EOatVLtqA==",
+ "version": "18.19.14",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz",
+ "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
From a67b173f9acdc103a2274f85382b1a53dcfd5d02 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 13:43:17 +0100
Subject: [PATCH 27/34] [PM-6067] [deps]: Lock file maintenance (#584)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Update | Change |
|---|---|
| lockFileMaintenance | All locks refreshed |
🔧 This Pull Request updates lock files to use the latest dependency
versions.
---
### Configuration
📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config help](https://togithub.com/renovatebot/renovate/discussions) if
that's undesired.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
[PM-6067](https://bitwarden.atlassian.net/browse/PM-6067)
[PM-6067]:
https://bitwarden.atlassian.net/browse/PM-6067?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
Cargo.lock | 75 +++++++++++++++----------
crates/bitwarden-napi/package-lock.json | 6 +-
package-lock.json | 6 +-
3 files changed, 51 insertions(+), 36 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 9054b7734..98274e54f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -78,9 +78,9 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
[[package]]
name = "anstyle-parse"
@@ -1242,9 +1242,9 @@ dependencies = [
[[package]]
name = "eyre"
-version = "0.6.11"
+version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [
"indenter",
"once_cell",
@@ -1502,7 +1502,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
"slab",
"tokio",
"tokio-util",
@@ -1661,9 +1661,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.59"
+version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -1717,9 +1717,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.2.1"
+version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
+checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
@@ -1848,9 +1848,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.152"
+version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libloading"
@@ -1949,9 +1949,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.7.1"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
@@ -1970,9 +1970,9 @@ dependencies = [
[[package]]
name = "napi"
-version = "2.15.0"
+version = "2.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efbf98e1bcb85cc441bbf7cdfb11070d2537a100e2697d75397b2584c32492d1"
+checksum = "43792514b0c95c5beec42996da0c1b39265b02b75c97baa82d163d3ef55cbfa7"
dependencies = [
"bitflags 2.4.2",
"ctor",
@@ -2082,6 +2082,12 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
[[package]]
name = "num-integer"
version = "0.1.45"
@@ -2315,7 +2321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef"
dependencies = [
"base64 0.21.7",
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
"line-wrap",
"quick-xml",
"serde",
@@ -2592,9 +2598,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
-version = "0.11.23"
+version = "0.11.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
+checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
dependencies = [
"base64 0.21.7",
"bytes",
@@ -2619,6 +2625,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
+ "sync_wrapper",
"system-configuration",
"tokio",
"tokio-rustls",
@@ -2688,9 +2695,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
-version = "0.38.30"
+version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
+checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.2",
"errno",
@@ -3022,7 +3029,7 @@ version = "0.9.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e"
dependencies = [
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
"itoa",
"ryu",
"serde",
@@ -3233,6 +3240,12 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
[[package]]
name = "syntect"
version = "5.1.0"
@@ -3335,12 +3348,13 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
+checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
dependencies = [
"deranged",
"itoa",
+ "num-conv",
"powerfmt",
"serde",
"time-core",
@@ -3355,10 +3369,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
+checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
dependencies = [
+ "num-conv",
"time-core",
]
@@ -3465,7 +3480,7 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [
- "indexmap 2.2.1",
+ "indexmap 2.2.2",
"serde",
"serde_spanned",
"toml_datetime",
@@ -3920,9 +3935,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.25.3"
+version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
+checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "weedle2"
@@ -4107,9 +4122,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
-version = "0.5.35"
+version = "0.5.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1931d78a9c73861da0134f453bb1f790ce49b2e30eba8410b4b79bac72b46a2d"
+checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5"
dependencies = [
"memchr",
]
diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json
index a8995bc77..9c9d87de9 100644
--- a/crates/bitwarden-napi/package-lock.json
+++ b/crates/bitwarden-napi/package-lock.json
@@ -95,9 +95,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.11.10",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz",
- "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==",
+ "version": "20.11.16",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
+ "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
"dev": true,
"peer": true,
"dependencies": {
diff --git a/package-lock.json b/package-lock.json
index 747d7d809..4ad23e90b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -346,9 +346,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.11.10",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz",
- "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==",
+ "version": "20.11.16",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
+ "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
"dev": true,
"peer": true,
"dependencies": {
From 43bdcf0a51696c4d8aa0e337d31214531db8bafe Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 14:06:55 +0100
Subject: [PATCH 28/34] [deps]: Update prettier to v3.2.5 (#583)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [prettier](https://prettier.io)
([source](https://togithub.com/prettier/prettier)) | [`3.2.4` ->
`3.2.5`](https://renovatebot.com/diffs/npm/prettier/3.2.4/3.2.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/prettier/3.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/prettier/3.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/prettier/3.2.4/3.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/prettier/3.2.4/3.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
---
### Release Notes
prettier/prettier (prettier)
###
[`v3.2.5`](https://togithub.com/prettier/prettier/blob/HEAD/CHANGELOG.md#325)
[Compare
Source](https://togithub.com/prettier/prettier/compare/3.2.4...3.2.5)
[diff](https://togithub.com/prettier/prettier/compare/3.2.4...3.2.5)
##### Support Angular inline styles as single template literal
([#15968](https://togithub.com/prettier/prettier/pull/15968) by
[@sosukesuzuki](https://togithub.com/sosukesuzuki))
[Angular
v17](https://blog.angular.io/introducing-angular-v17-4d7033312e4b)
supports single string inline styles.
```ts
// Input
@Component({
template: `...
`,
styles: `h1 { color: blue; }`,
})
export class AppComponent {}
// Prettier 3.2.4
@Component({
template: `...
`,
styles: `h1 { color: blue; }`,
})
export class AppComponent {}
// Prettier 3.2.5
@Component({
template: `...
`,
styles: `
h1 {
color: blue;
}
`,
})
export class AppComponent {}
```
##### Unexpected embedded formatting for Angular template
([#15969](https://togithub.com/prettier/prettier/pull/15969) by
[@JounQin](https://togithub.com/JounQin))
Computed template should not be considered as Angular component template
```ts
// Input
const template = "foobar";
@Component({
[template]: `{{ hello }}
`,
})
export class AppComponent {}
// Prettier 3.2.4
const template = "foobar";
@Component({
[template]: `{{ hello }}
`,
})
export class AppComponent {}
// Prettier 3.2.5
const template = "foobar";
@Component({
[template]: `{{ hello }}
`,
})
export class AppComponent {}
```
##### Use `"json"` parser for `tsconfig.json` by default
([#16012](https://togithub.com/prettier/prettier/pull/16012) by
[@sosukesuzuki](https://togithub.com/sosukesuzuki))
In
[v2.3.0](https://prettier.io/blog/2024/01/12/3.2.0#new-jsonc-parser-added-15831httpsgithubcomprettierprettierpull15831-by-fiskerhttpsgithubcomfisker),
we introduced `"jsonc"` parser which adds trialing comma **by default**.
When adding a new parser we also define how it will be used based on the
[`linguist-languages`](https://www.npmjs.com/package/linguist-languages)
data.
`tsconfig.json` is a special file used by
[TypeScript](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#using-tsconfigjson-or-jsconfigjson),
it uses `.json` file extension, but it actually uses the [JSON with
Comments](https://code.visualstudio.com/docs/languages/json#\_json-with-comments)
syntax. However, we found that there are many third-party tools not
recognize it correctly because of the confusing `.json` file extension.
We decide to treat it as a JSON file for now to avoid the extra
configuration step.
To keep using the `"jsonc"` parser for your `tsconfig.json` files, add
the following to your `.pretterrc` file
```json
{
"overrides": [
{
"files": ["tsconfig.json", "jsconfig.json"],
"options": {
"parser": "jsonc"
}
}
]
}
```
```
```
---
### Configuration
📅 **Schedule**: Branch creation - "every weekend" (UTC), Automerge - At
any time (no schedule defined).
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
â™» **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/bitwarden/sdk).
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Hinton
---
crates/bitwarden-napi/tsconfig.json | 4 ++--
package-lock.json | 8 ++++----
package.json | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/crates/bitwarden-napi/tsconfig.json b/crates/bitwarden-napi/tsconfig.json
index 8ec7e00d4..f977e0759 100644
--- a/crates/bitwarden-napi/tsconfig.json
+++ b/crates/bitwarden-napi/tsconfig.json
@@ -7,7 +7,7 @@
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
- "declaration": true,
+ "declaration": true
},
- "include": ["src-ts", "src-ts/bitwarden_client", "src-ts/index.ts"],
+ "include": ["src-ts", "src-ts/bitwarden_client", "src-ts/index.ts"]
}
diff --git a/package-lock.json b/package-lock.json
index 4ad23e90b..bdb82563e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
"devDependencies": {
"@openapitools/openapi-generator-cli": "2.9.0",
"handlebars": "^4.7.8",
- "prettier": "3.2.4",
+ "prettier": "3.2.5",
"quicktype-core": "23.0.81",
"rimraf": "5.0.5",
"ts-node": "10.9.2",
@@ -1471,9 +1471,9 @@
}
},
"node_modules/prettier": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
- "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+ "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
diff --git a/package.json b/package.json
index 14b5692a0..05195270e 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
"devDependencies": {
"@openapitools/openapi-generator-cli": "2.9.0",
"handlebars": "^4.7.8",
- "prettier": "3.2.4",
+ "prettier": "3.2.5",
"quicktype-core": "23.0.81",
"rimraf": "5.0.5",
"ts-node": "10.9.2",
From 11e36557dcb86c7cb376d278124371fb6e764b7d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 09:02:30 -0500
Subject: [PATCH 29/34] [deps]: Update codecov/codecov-action action to v4
(#580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/workflows/rust-test.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/rust-test.yml b/.github/workflows/rust-test.yml
index 93a2ddecf..8408dd1d7 100644
--- a/.github/workflows/rust-test.yml
+++ b/.github/workflows/rust-test.yml
@@ -73,7 +73,7 @@ jobs:
run: cargo llvm-cov --all-features --lcov --output-path lcov.info --ignore-filename-regex "crates/bitwarden-api-"
- name: Upload to codecov.io
- uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5
+ uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
From 46c72f3dbadc3dce09acb1775f7033b2a3ba882a Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 09:13:05 -0500
Subject: [PATCH 30/34] [deps]: Update ruby/setup-ruby action to v1.171.0
(#578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/workflows/publish-ruby.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml
index 81aedcc22..12abd18f0 100644
--- a/.github/workflows/publish-ruby.yml
+++ b/.github/workflows/publish-ruby.yml
@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Ruby
- uses: ruby/setup-ruby@bd03e04863f52d169e18a2b190e8fa6b84938215 # v1.170.0
+ uses: ruby/setup-ruby@22fdc77bf4148f810455b226c90fb81b5cbc00a7 # v1.171.0
with:
ruby-version: 3.2
From 69a5d7bf01b17aeccb3be7bc77272bf50cb24de9 Mon Sep 17 00:00:00 2001
From: Vince Grassia <593223+vgrassia@users.noreply.github.com>
Date: Mon, 5 Feb 2024 10:46:47 -0500
Subject: [PATCH 31/34] Update Gradle action as recommended in new release
(#585)
---
.github/workflows/build-android.yml | 2 +-
.github/workflows/build-java.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml
index e1aeea244..efa8c7bf0 100644
--- a/.github/workflows/build-android.yml
+++ b/.github/workflows/build-android.yml
@@ -102,7 +102,7 @@ jobs:
run: ./build-schemas.sh
- name: Publish
- uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
+ uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
with:
arguments: sdk:publish
build-root-directory: languages/kotlin
diff --git a/.github/workflows/build-java.yml b/.github/workflows/build-java.yml
index 0ed8997ca..2559f659c 100644
--- a/.github/workflows/build-java.yml
+++ b/.github/workflows/build-java.yml
@@ -61,7 +61,7 @@ jobs:
path: languages/java/src/main/resources/win32-x86-64
- name: Publish Maven
- uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
+ uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
with:
arguments: publish
build-root-directory: languages/java
From b98a019482d06e8c89afeceded1b6bffc32bd127 Mon Sep 17 00:00:00 2001
From: Oscar Hinton
Date: Tue, 6 Feb 2024 10:34:43 +0100
Subject: [PATCH 32/34] Use base renovate config (#586)
Use shared renovate config.
---
.github/renovate.json | 15 +--------------
1 file changed, 1 insertion(+), 14 deletions(-)
diff --git a/.github/renovate.json b/.github/renovate.json
index ae4ad0e9d..ddfc900dc 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -1,21 +1,8 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": [
- "config:base",
- "github>bitwarden/renovate-config:pin-actions",
- ":combinePatchMinorReleases",
- ":dependencyDashboard",
- ":maintainLockFilesWeekly",
- ":prConcurrentLimit10",
- ":rebaseStalePrs",
- ":separateMajorReleases",
- "group:monorepos",
- "schedule:weekends"
- ],
+ "extends": ["github>bitwarden/renovate-config:non-pinned"],
"separateMajorMinor": true,
"enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
- "commitMessagePrefix": "[deps]:",
- "commitMessageTopic": "{{depName}}",
"packageRules": [
{
"matchManagers": ["cargo"],
From 714c2d40d1811c13c751b6de9a9fe0ae51f61583 Mon Sep 17 00:00:00 2001
From: Oscar Hinton
Date: Tue, 6 Feb 2024 11:52:38 +0100
Subject: [PATCH 33/34] [PM-3438] Vault exports (#561)
Vault export implementation.
New crate `bitwarden-exporters` which defines it's own data structure,
isolating it from the other code areas. It primarily focuses on
converting a data model to csv and json. Currently all data has to be
decrypted independently of what is required for the export which is an
area of improvement for the future.
---
.prettierignore | 3 +
.vscode/settings.json | 2 +
Cargo.lock | 34 +
crates/bitwarden-exporters/Cargo.toml | 26 +
crates/bitwarden-exporters/README.md | 6 +
.../resources/json_export.json | 146 ++++
crates/bitwarden-exporters/src/csv.rs | 266 ++++++
crates/bitwarden-exporters/src/json.rs | 762 ++++++++++++++++++
crates/bitwarden-exporters/src/lib.rs | 142 ++++
crates/bitwarden/Cargo.toml | 1 +
crates/bitwarden/src/error.rs | 5 +
.../src/tool/exporters/client_exporter.rs | 6 +-
crates/bitwarden/src/tool/exporters/mod.rs | 278 ++++++-
crates/bitwarden/src/vault/cipher/field.rs | 8 +-
.../bitwarden/src/vault/cipher/linked_id.rs | 8 +-
crates/bitwarden/src/vault/cipher/mod.rs | 7 +-
.../bitwarden/src/vault/cipher/secure_note.rs | 2 +-
crates/bitwarden/src/vault/folder.rs | 6 +-
crates/bitwarden/src/vault/mod.rs | 7 +-
19 files changed, 1685 insertions(+), 30 deletions(-)
create mode 100644 crates/bitwarden-exporters/Cargo.toml
create mode 100644 crates/bitwarden-exporters/README.md
create mode 100644 crates/bitwarden-exporters/resources/json_export.json
create mode 100644 crates/bitwarden-exporters/src/csv.rs
create mode 100644 crates/bitwarden-exporters/src/json.rs
create mode 100644 crates/bitwarden-exporters/src/lib.rs
diff --git a/.prettierignore b/.prettierignore
index d5ffe5a0e..97474cca9 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -7,3 +7,6 @@ schemas
/crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts
about.hbs
support/docs/template.hbs
+
+# Test fixtures
+crates/bitwarden-exporters/resources/*
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 63c3dccaa..e92fcfb76 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -15,8 +15,10 @@
"Pbkdf",
"PKCS8",
"repr",
+ "reprompt",
"reqwest",
"schemars",
+ "totp",
"uniffi",
"wordlist",
"zxcvbn"
diff --git a/Cargo.lock b/Cargo.lock
index 98274e54f..b2728124b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -352,6 +352,7 @@ dependencies = [
"bitwarden-api-api",
"bitwarden-api-identity",
"bitwarden-crypto",
+ "bitwarden-exporters",
"bitwarden-generators",
"chrono",
"getrandom 0.2.12",
@@ -451,6 +452,18 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "bitwarden-exporters"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "csv",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "uuid",
+]
+
[[package]]
name = "bitwarden-generators"
version = "0.1.0"
@@ -999,6 +1012,27 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "csv"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "ctor"
version = "0.2.6"
diff --git a/crates/bitwarden-exporters/Cargo.toml b/crates/bitwarden-exporters/Cargo.toml
new file mode 100644
index 000000000..0008fbb49
--- /dev/null
+++ b/crates/bitwarden-exporters/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "bitwarden-exporters"
+version = "0.1.0"
+authors = ["Bitwarden Inc"]
+license-file = "LICENSE"
+repository = "https://github.com/bitwarden/sdk"
+homepage = "https://bitwarden.com"
+description = """
+Internal crate for the bitwarden crate. Do not use.
+"""
+keywords = ["bitwarden"]
+edition = "2021"
+rust-version = "1.57"
+exclude = ["/resources"]
+
+[dependencies]
+chrono = { version = ">=0.4.26, <0.5", features = [
+ "clock",
+ "serde",
+ "std",
+], default-features = false }
+csv = "1.3.0"
+serde = { version = ">=1.0, <2.0", features = ["derive"] }
+serde_json = ">=1.0.96, <2.0"
+thiserror = ">=1.0.40, <2.0"
+uuid = { version = ">=1.3.3, <2.0", features = ["serde"] }
diff --git a/crates/bitwarden-exporters/README.md b/crates/bitwarden-exporters/README.md
new file mode 100644
index 000000000..59936680f
--- /dev/null
+++ b/crates/bitwarden-exporters/README.md
@@ -0,0 +1,6 @@
+# Bitwarden Exporters
+
+This is an internal crate for the Bitwarden SDK do not depend on this directly and use the
+[`bitwarden`](https://crates.io/crates/bitwarden) crate instead.
+
+This crate does not follow semantic versioning and the public interface may change at any time.
diff --git a/crates/bitwarden-exporters/resources/json_export.json b/crates/bitwarden-exporters/resources/json_export.json
new file mode 100644
index 000000000..ad5380550
--- /dev/null
+++ b/crates/bitwarden-exporters/resources/json_export.json
@@ -0,0 +1,146 @@
+{
+ "encrypted": false,
+ "folders": [
+ {
+ "id": "942e2984-1b9a-453b-b039-b107012713b9",
+ "name": "Important"
+ }
+ ],
+ "items": [
+ {
+ "id": "25c8c414-b446-48e9-a1bd-b10700bbd740",
+ "folderId": "942e2984-1b9a-453b-b039-b107012713b9",
+ "organizationId": null,
+ "collectionIds": null,
+ "name": "Bitwarden",
+ "notes": "My note",
+ "type": 1,
+ "login": {
+ "username": "test@bitwarden.com",
+ "password": "asdfasdfasdf",
+ "uris": [
+ {
+ "uri": "https://vault.bitwarden.com",
+ "match": null
+ }
+ ],
+ "totp": "ABC",
+ "fido2Credentials": []
+ },
+ "favorite": true,
+ "reprompt": 0,
+ "fields": [
+ {
+ "name": "Text",
+ "value": "A",
+ "type": 0,
+ "linkedId": null
+ },
+ {
+ "name": "Hidden",
+ "value": "B",
+ "type": 1,
+ "linkedId": null
+ },
+ {
+ "name": "Boolean (true)",
+ "value": "true",
+ "type": 2,
+ "linkedId": null
+ },
+ {
+ "name": "Boolean (false)",
+ "value": "false",
+ "type": 2,
+ "linkedId": null
+ },
+ {
+ "name": "Linked",
+ "value": null,
+ "type": 3,
+ "linkedId": 101
+ }
+ ],
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T14:09:33.753Z",
+ "creationDate": "2024-01-30T11:23:54.416Z",
+ "deletedDate": null
+ },
+ {
+ "id": "23f0f877-42b1-4820-a850-b10700bc41eb",
+ "folderId": null,
+ "organizationId": null,
+ "collectionIds": null,
+ "name": "My secure note",
+ "notes": "Very secure!",
+ "type": 2,
+ "secureNote": {
+ "type": 0
+ },
+ "favorite": false,
+ "reprompt": 0,
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T11:25:25.466Z",
+ "creationDate": "2024-01-30T11:25:25.466Z",
+ "deletedDate": null
+ },
+ {
+ "id": "3ed8de45-48ee-4e26-a2dc-b10701276c53",
+ "folderId": null,
+ "organizationId": null,
+ "collectionIds": null,
+ "name": "My card",
+ "notes": null,
+ "type": 3,
+ "card": {
+ "cardholderName": "John Doe",
+ "expMonth": "1",
+ "expYear": "2032",
+ "code": "123",
+ "brand": "Visa",
+ "number": "4111111111111111"
+ },
+ "favorite": false,
+ "reprompt": 0,
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T17:55:36.150Z",
+ "creationDate": "2024-01-30T17:55:36.150Z",
+ "deletedDate": null
+ },
+ {
+ "id": "41cc3bc1-c3d9-4637-876c-b10701273712",
+ "folderId": "942e2984-1b9a-453b-b039-b107012713b9",
+ "organizationId": null,
+ "collectionIds": null,
+ "name": "My identity",
+ "notes": null,
+ "type": 4,
+ "identity": {
+ "title": "Mr",
+ "firstName": "John",
+ "middleName": null,
+ "lastName": "Doe",
+ "address1": null,
+ "address2": null,
+ "address3": null,
+ "city": null,
+ "state": null,
+ "postalCode": null,
+ "country": null,
+ "company": "Bitwarden",
+ "email": null,
+ "phone": null,
+ "ssn": null,
+ "username": "JDoe",
+ "passportNumber": null,
+ "licenseNumber": null
+ },
+ "favorite": false,
+ "reprompt": 0,
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T17:54:50.706Z",
+ "creationDate": "2024-01-30T17:54:50.706Z",
+ "deletedDate": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/crates/bitwarden-exporters/src/csv.rs b/crates/bitwarden-exporters/src/csv.rs
new file mode 100644
index 000000000..644eeb030
--- /dev/null
+++ b/crates/bitwarden-exporters/src/csv.rs
@@ -0,0 +1,266 @@
+use std::collections::HashMap;
+
+use csv::Writer;
+use serde::Serializer;
+use thiserror::Error;
+use uuid::Uuid;
+
+use crate::{Cipher, CipherType, Field, Folder};
+
+#[derive(Debug, Error)]
+pub enum CsvError {
+ #[error("CSV error")]
+ Csv,
+}
+
+pub(crate) fn export_csv(folders: Vec, ciphers: Vec) -> Result {
+ let folders: HashMap = folders.into_iter().map(|f| (f.id, f.name)).collect();
+
+ let rows = ciphers
+ .into_iter()
+ .filter(|c| matches!(c.r#type, CipherType::Login(_) | CipherType::SecureNote(_)))
+ .map(|c| {
+ let login = if let CipherType::Login(l) = &c.r#type {
+ Some(l)
+ } else {
+ None
+ };
+
+ CsvRow {
+ folder: c
+ .folder_id
+ .and_then(|f| folders.get(&f))
+ .map(|f| f.to_owned()),
+ favorite: c.favorite,
+ r#type: c.r#type.to_string(),
+ name: c.name.to_owned(),
+ notes: c.notes.to_owned(),
+ fields: c.fields,
+ reprompt: c.reprompt,
+ login_uri: login
+ .map(|l| l.login_uris.iter().flat_map(|l| l.uri.clone()).collect())
+ .unwrap_or_default(),
+ login_username: login.and_then(|l| l.username.clone()),
+ login_password: login.and_then(|l| l.password.clone()),
+ login_totp: login.and_then(|l| l.totp.clone()),
+ }
+ });
+
+ let mut wtr = Writer::from_writer(vec![]);
+ for row in rows {
+ wtr.serialize(row).unwrap();
+ }
+
+ String::from_utf8(wtr.into_inner().map_err(|_| CsvError::Csv)?).map_err(|_| CsvError::Csv)
+}
+
+/// CSV export format. See https://bitwarden.com/help/condition-bitwarden-import/#condition-a-csv
+///
+/// Be careful when changing this struct to maintain compatibility with old exports.
+#[derive(serde::Serialize)]
+struct CsvRow {
+ folder: Option,
+ #[serde(serialize_with = "bool_serialize")]
+ favorite: bool,
+ r#type: String,
+ name: String,
+ notes: Option,
+ #[serde(serialize_with = "fields_serialize")]
+ fields: Vec,
+ reprompt: u8,
+ #[serde(serialize_with = "vec_serialize")]
+ login_uri: Vec,
+ login_username: Option,
+ login_password: Option,
+ login_totp: Option,
+}
+
+fn vec_serialize(x: &[String], s: S) -> Result
+where
+ S: Serializer,
+{
+ s.serialize_str(x.join(",").as_str())
+}
+
+fn bool_serialize(x: &bool, s: S) -> Result
+where
+ S: Serializer,
+{
+ s.serialize_str(if *x { "1" } else { "" })
+}
+
+fn fields_serialize(x: &[Field], s: S) -> Result
+where
+ S: Serializer,
+{
+ s.serialize_str(
+ x.iter()
+ .map(|f| {
+ format!(
+ "{}: {}",
+ f.name.to_owned().unwrap_or_default(),
+ f.value.to_owned().unwrap_or_default()
+ )
+ })
+ .collect::>()
+ .join("\n")
+ .as_str(),
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{Card, Identity, Login, LoginUri};
+
+ #[test]
+ fn test_export_csv() {
+ let folders = vec![
+ Folder {
+ id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
+ name: "Test Folder A".to_string(),
+ },
+ Folder {
+ id: "583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap(),
+ name: "Test Folder B".to_string(),
+ },
+ ];
+ let ciphers = vec![
+ Cipher {
+ id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
+ folder_id: None,
+ name: "test@bitwarden.com".to_string(),
+ notes: None,
+ r#type: CipherType::Login(Box::new(Login {
+ username: Some("test@bitwarden.com".to_string()),
+ password: Some("Abc123".to_string()),
+ login_uris: vec![LoginUri {
+ uri: Some("https://google.com".to_string()),
+ r#match: None,
+ }],
+ totp: None,
+ })),
+ favorite: false,
+ reprompt: 0,
+ fields: vec![],
+ revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ deleted_date: None,
+ },
+ Cipher {
+ id: "7dd81bd0-cc72-4f42-96e7-b0fc014e71a3".parse().unwrap(),
+ folder_id: Some("583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap()),
+ name: "Steam Account".to_string(),
+ notes: None,
+ r#type: CipherType::Login(Box::new(Login {
+ username: Some("steam".to_string()),
+ password: Some("3Pvb8u7EfbV*nJ".to_string()),
+ login_uris: vec![LoginUri {
+ uri: Some("https://steampowered.com".to_string()),
+ r#match: None,
+ }],
+ totp: Some("steam://ABCD123".to_string()),
+ })),
+ favorite: true,
+ reprompt: 0,
+ fields: vec![
+ Field {
+ name: Some("Test".to_string()),
+ value: Some("v".to_string()),
+ r#type: 0,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Hidden".to_string()),
+ value: Some("asdfer".to_string()),
+ r#type: 1,
+ linked_id: None,
+ },
+ ],
+ revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ deleted_date: None,
+ },
+ ];
+
+ let csv = export_csv(folders, ciphers).unwrap();
+ let expected = [
+ "folder,favorite,type,name,notes,fields,reprompt,login_uri,login_username,login_password,login_totp",
+ ",,login,test@bitwarden.com,,,0,https://google.com,test@bitwarden.com,Abc123,",
+ "Test Folder B,1,login,Steam Account,,\"Test: v\nHidden: asdfer\",0,https://steampowered.com,steam,3Pvb8u7EfbV*nJ,steam://ABCD123",
+ "",
+ ].join("\n");
+
+ assert_eq!(csv, expected);
+ }
+
+ #[test]
+ fn test_export_ignore_card() {
+ let folders = vec![];
+ let ciphers = vec![Cipher {
+ id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
+ folder_id: None,
+ name: "My Card".to_string(),
+ notes: None,
+ r#type: CipherType::Card(Box::new(Card {
+ cardholder_name: None,
+ exp_month: None,
+ exp_year: None,
+ code: None,
+ brand: None,
+ number: None,
+ })),
+ favorite: false,
+ reprompt: 0,
+ fields: vec![],
+ revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ deleted_date: None,
+ }];
+
+ let csv = export_csv(folders, ciphers).unwrap();
+
+ assert_eq!(csv, "");
+ }
+
+ #[test]
+ fn test_export_ignore_identity() {
+ let folders = vec![];
+ let ciphers = vec![Cipher {
+ id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
+ folder_id: None,
+ name: "My Identity".to_string(),
+ notes: None,
+ r#type: CipherType::Identity(Box::new(Identity {
+ title: None,
+ first_name: None,
+ middle_name: None,
+ last_name: None,
+ address1: None,
+ address2: None,
+ address3: None,
+ city: None,
+ state: None,
+ postal_code: None,
+ country: None,
+ company: None,
+ email: None,
+ phone: None,
+ ssn: None,
+ username: None,
+ passport_number: None,
+ license_number: None,
+ })),
+ favorite: false,
+ reprompt: 0,
+ fields: vec![],
+ revision_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:28:20.036Z".parse().unwrap(),
+ deleted_date: None,
+ }];
+
+ let csv = export_csv(folders, ciphers).unwrap();
+
+ assert_eq!(csv, "");
+ }
+}
diff --git a/crates/bitwarden-exporters/src/json.rs b/crates/bitwarden-exporters/src/json.rs
new file mode 100644
index 000000000..3f6c72c1f
--- /dev/null
+++ b/crates/bitwarden-exporters/src/json.rs
@@ -0,0 +1,762 @@
+use chrono::{DateTime, Utc};
+use thiserror::Error;
+use uuid::Uuid;
+
+use crate::{Card, Cipher, CipherType, Field, Folder, Identity, Login, LoginUri, SecureNote};
+
+#[derive(Error, Debug)]
+pub enum JsonError {
+ #[error("JSON error: {0}")]
+ Serde(#[from] serde_json::Error),
+}
+
+pub(crate) fn export_json(folders: Vec, ciphers: Vec) -> Result {
+ let export = JsonExport {
+ encrypted: false,
+ folders: folders.into_iter().map(|f| f.into()).collect(),
+ items: ciphers.into_iter().map(|c| c.into()).collect(),
+ };
+
+ Ok(serde_json::to_string_pretty(&export)?)
+}
+
+/// JSON export format. These are intentionally decoupled from the internal data structures to
+/// ensure internal changes are not reflected in the public exports.
+///
+/// Be careful about changing these structs to maintain compatibility with old exporters/importers.
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonExport {
+ encrypted: bool,
+ folders: Vec,
+ items: Vec,
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonFolder {
+ id: Uuid,
+ name: String,
+}
+
+impl From for JsonFolder {
+ fn from(folder: Folder) -> Self {
+ JsonFolder {
+ id: folder.id,
+ name: folder.name,
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonCipher {
+ id: Uuid,
+ folder_id: Option,
+ // Organizational IDs which are always empty in personal exports
+ organization_id: Option,
+ collection_ids: Option>,
+
+ name: String,
+ notes: Option,
+
+ r#type: u8,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ login: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ identity: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ card: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ secure_note: Option,
+
+ favorite: bool,
+ reprompt: u8,
+
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ fields: Vec,
+ password_history: Option>,
+
+ revision_date: DateTime,
+ creation_date: DateTime,
+ deleted_date: Option>,
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonLogin {
+ username: Option,
+ password: Option,
+ uris: Vec,
+ totp: Option,
+ fido2_credentials: Vec,
+}
+
+impl From for JsonLogin {
+ fn from(login: Login) -> Self {
+ JsonLogin {
+ username: login.username,
+ password: login.password,
+ uris: login.login_uris.into_iter().map(|u| u.into()).collect(),
+ totp: login.totp,
+ fido2_credentials: vec![],
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonLoginUri {
+ uri: Option,
+ r#match: Option,
+}
+
+impl From for JsonLoginUri {
+ fn from(login_uri: LoginUri) -> Self {
+ JsonLoginUri {
+ uri: login_uri.uri,
+ r#match: login_uri.r#match,
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonSecureNote {
+ r#type: u8,
+}
+
+impl From for JsonSecureNote {
+ fn from(note: SecureNote) -> Self {
+ JsonSecureNote {
+ r#type: note.r#type as u8,
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonCard {
+ cardholder_name: Option,
+ exp_month: Option,
+ exp_year: Option,
+ code: Option,
+ brand: Option,
+ number: Option,
+}
+
+impl From for JsonCard {
+ fn from(card: Card) -> Self {
+ JsonCard {
+ cardholder_name: card.cardholder_name,
+ exp_month: card.exp_month,
+ exp_year: card.exp_year,
+ code: card.code,
+ brand: card.brand,
+ number: card.number,
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonIdentity {
+ title: Option,
+ first_name: Option,
+ middle_name: Option,
+ last_name: Option,
+ address1: Option,
+ address2: Option,
+ address3: Option,
+ city: Option,
+ state: Option,
+ postal_code: Option,
+ country: Option,
+ company: Option,
+ email: Option,
+ phone: Option,
+ ssn: Option,
+ username: Option,
+ passport_number: Option,
+ license_number: Option,
+}
+
+impl From for JsonIdentity {
+ fn from(identity: Identity) -> Self {
+ JsonIdentity {
+ title: identity.title,
+ first_name: identity.first_name,
+ middle_name: identity.middle_name,
+ last_name: identity.last_name,
+ address1: identity.address1,
+ address2: identity.address2,
+ address3: identity.address3,
+ city: identity.city,
+ state: identity.state,
+ postal_code: identity.postal_code,
+ country: identity.country,
+ company: identity.company,
+ email: identity.email,
+ phone: identity.phone,
+ ssn: identity.ssn,
+ username: identity.username,
+ passport_number: identity.passport_number,
+ license_number: identity.license_number,
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct JsonField {
+ name: Option,
+ value: Option,
+ r#type: u8,
+ linked_id: Option,
+}
+
+impl From for JsonField {
+ fn from(field: Field) -> Self {
+ JsonField {
+ name: field.name,
+ value: field.value,
+ r#type: field.r#type,
+ linked_id: field.linked_id,
+ }
+ }
+}
+
+impl From for JsonCipher {
+ fn from(cipher: Cipher) -> Self {
+ let r#type = match cipher.r#type {
+ CipherType::Login(_) => 1,
+ CipherType::SecureNote(_) => 2,
+ CipherType::Card(_) => 3,
+ CipherType::Identity(_) => 4,
+ };
+
+ let (login, secure_note, card, identity) = match cipher.r#type {
+ CipherType::Login(l) => (Some((*l).into()), None, None, None),
+ CipherType::SecureNote(s) => (None, Some((*s).into()), None, None),
+ CipherType::Card(c) => (None, None, Some((*c).into()), None),
+ CipherType::Identity(i) => (None, None, None, Some((*i).into())),
+ };
+
+ JsonCipher {
+ id: cipher.id,
+ folder_id: cipher.folder_id,
+ organization_id: None,
+ collection_ids: None,
+ name: cipher.name,
+ notes: cipher.notes,
+ r#type,
+ login,
+ identity,
+ card,
+ secure_note,
+ favorite: cipher.favorite,
+ reprompt: cipher.reprompt,
+ fields: cipher.fields.into_iter().map(|f| f.into()).collect(),
+ password_history: None,
+ revision_date: cipher.revision_date,
+ creation_date: cipher.creation_date,
+ deleted_date: cipher.deleted_date,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{fs, io::Read, path::PathBuf};
+
+ use super::*;
+ use crate::{Cipher, Field, LoginUri, SecureNoteType};
+
+ #[test]
+ fn test_convert_login() {
+ let cipher = Cipher {
+ id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(),
+ folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()),
+
+ name: "Bitwarden".to_string(),
+ notes: Some("My note".to_string()),
+
+ r#type: CipherType::Login(Box::new(Login {
+ username: Some("test@bitwarden.com".to_string()),
+ password: Some("asdfasdfasdf".to_string()),
+ login_uris: vec![LoginUri {
+ uri: Some("https://vault.bitwarden.com".to_string()),
+ r#match: None,
+ }],
+ totp: Some("ABC".to_string()),
+ })),
+
+ favorite: true,
+ reprompt: 0,
+
+ fields: vec![
+ Field {
+ name: Some("Text".to_string()),
+ value: Some("A".to_string()),
+ r#type: 0,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Hidden".to_string()),
+ value: Some("B".to_string()),
+ r#type: 1,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Boolean (true)".to_string()),
+ value: Some("true".to_string()),
+ r#type: 2,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Boolean (false)".to_string()),
+ value: Some("false".to_string()),
+ r#type: 2,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Linked".to_string()),
+ value: None,
+ r#type: 3,
+ linked_id: Some(101),
+ },
+ ],
+
+ revision_date: "2024-01-30T14:09:33.753Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:23:54.416Z".parse().unwrap(),
+ deleted_date: None,
+ };
+
+ let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap();
+
+ let expected = r#"{
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T14:09:33.753Z",
+ "creationDate": "2024-01-30T11:23:54.416Z",
+ "deletedDate": null,
+ "id": "25c8c414-b446-48e9-a1bd-b10700bbd740",
+ "organizationId": null,
+ "folderId": "942e2984-1b9a-453b-b039-b107012713b9",
+ "type": 1,
+ "reprompt": 0,
+ "name": "Bitwarden",
+ "notes": "My note",
+ "favorite": true,
+ "fields": [
+ {
+ "name": "Text",
+ "value": "A",
+ "type": 0,
+ "linkedId": null
+ },
+ {
+ "name": "Hidden",
+ "value": "B",
+ "type": 1,
+ "linkedId": null
+ },
+ {
+ "name": "Boolean (true)",
+ "value": "true",
+ "type": 2,
+ "linkedId": null
+ },
+ {
+ "name": "Boolean (false)",
+ "value": "false",
+ "type": 2,
+ "linkedId": null
+ },
+ {
+ "name": "Linked",
+ "value": null,
+ "type": 3,
+ "linkedId": 101
+ }
+ ],
+ "login": {
+ "fido2Credentials": [],
+ "uris": [
+ {
+ "match": null,
+ "uri": "https://vault.bitwarden.com"
+ }
+ ],
+ "username": "test@bitwarden.com",
+ "password": "asdfasdfasdf",
+ "totp": "ABC"
+ },
+ "collectionIds": null
+ }"#;
+
+ assert_eq!(
+ json.parse::().unwrap(),
+ expected.parse::().unwrap()
+ )
+ }
+
+ #[test]
+ fn test_convert_secure_note() {
+ let cipher = Cipher {
+ id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(),
+ folder_id: None,
+
+ name: "My secure note".to_string(),
+ notes: Some("Very secure!".to_string()),
+
+ r#type: CipherType::SecureNote(Box::new(SecureNote {
+ r#type: SecureNoteType::Generic,
+ })),
+
+ favorite: false,
+ reprompt: 0,
+
+ fields: vec![],
+
+ revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(),
+ deleted_date: None,
+ };
+
+ let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap();
+
+ let expected = r#"{
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T11:25:25.466Z",
+ "creationDate": "2024-01-30T11:25:25.466Z",
+ "deletedDate": null,
+ "id": "23f0f877-42b1-4820-a850-b10700bc41eb",
+ "organizationId": null,
+ "folderId": null,
+ "type": 2,
+ "reprompt": 0,
+ "name": "My secure note",
+ "notes": "Very secure!",
+ "favorite": false,
+ "secureNote": {
+ "type": 0
+ },
+ "collectionIds": null
+ }"#;
+
+ assert_eq!(
+ json.parse::().unwrap(),
+ expected.parse::().unwrap()
+ )
+ }
+
+ #[test]
+ fn test_convert_card() {
+ let cipher = Cipher {
+ id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(),
+ folder_id: None,
+
+ name: "My card".to_string(),
+ notes: None,
+
+ r#type: CipherType::Card(Box::new(Card {
+ cardholder_name: Some("John Doe".to_string()),
+ exp_month: Some("1".to_string()),
+ exp_year: Some("2032".to_string()),
+ code: Some("123".to_string()),
+ brand: Some("Visa".to_string()),
+ number: Some("4111111111111111".to_string()),
+ })),
+
+ favorite: false,
+ reprompt: 0,
+
+ fields: vec![],
+
+ revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
+ creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
+ deleted_date: None,
+ };
+
+ let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap();
+
+ let expected = r#"{
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T17:55:36.150Z",
+ "creationDate": "2024-01-30T17:55:36.150Z",
+ "deletedDate": null,
+ "id": "3ed8de45-48ee-4e26-a2dc-b10701276c53",
+ "organizationId": null,
+ "folderId": null,
+ "type": 3,
+ "reprompt": 0,
+ "name": "My card",
+ "notes": null,
+ "favorite": false,
+ "card": {
+ "cardholderName": "John Doe",
+ "brand": "Visa",
+ "number": "4111111111111111",
+ "expMonth": "1",
+ "expYear": "2032",
+ "code": "123"
+ },
+ "collectionIds": null
+ }"#;
+
+ assert_eq!(
+ json.parse::().unwrap(),
+ expected.parse::().unwrap()
+ )
+ }
+
+ #[test]
+ fn test_convert_identity() {
+ let cipher = Cipher {
+ id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(),
+ folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()),
+
+ name: "My identity".to_string(),
+ notes: None,
+
+ r#type: CipherType::Identity(Box::new(Identity {
+ title: Some("Mr".to_string()),
+ first_name: Some("John".to_string()),
+ middle_name: None,
+ last_name: Some("Doe".to_string()),
+ address1: None,
+ address2: None,
+ address3: None,
+ city: None,
+ state: None,
+ postal_code: None,
+ country: None,
+ company: Some("Bitwarden".to_string()),
+ email: None,
+ phone: None,
+ ssn: None,
+ username: Some("JDoe".to_string()),
+ passport_number: None,
+ license_number: None,
+ })),
+
+ favorite: false,
+ reprompt: 0,
+
+ fields: vec![],
+
+ revision_date: "2024-01-30T17:54:50.706Z".parse().unwrap(),
+ creation_date: "2024-01-30T17:54:50.706Z".parse().unwrap(),
+ deleted_date: None,
+ };
+
+ let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap();
+
+ let expected = r#"{
+ "passwordHistory": null,
+ "revisionDate": "2024-01-30T17:54:50.706Z",
+ "creationDate": "2024-01-30T17:54:50.706Z",
+ "deletedDate": null,
+ "id": "41cc3bc1-c3d9-4637-876c-b10701273712",
+ "organizationId": null,
+ "folderId": "942e2984-1b9a-453b-b039-b107012713b9",
+ "type": 4,
+ "reprompt": 0,
+ "name": "My identity",
+ "notes": null,
+ "favorite": false,
+ "identity": {
+ "title": "Mr",
+ "firstName": "John",
+ "middleName": null,
+ "lastName": "Doe",
+ "address1": null,
+ "address2": null,
+ "address3": null,
+ "city": null,
+ "state": null,
+ "postalCode": null,
+ "country": null,
+ "company": "Bitwarden",
+ "email": null,
+ "phone": null,
+ "ssn": null,
+ "username": "JDoe",
+ "passportNumber": null,
+ "licenseNumber": null
+ },
+ "collectionIds": null
+ }"#;
+
+ assert_eq!(
+ json.parse::().unwrap(),
+ expected.parse::().unwrap()
+ )
+ }
+
+ #[test]
+ pub fn test_export() {
+ let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ d.push("resources");
+ d.push("json_export.json");
+
+ let mut file = fs::File::open(d).unwrap();
+
+ let mut expected = String::new();
+ file.read_to_string(&mut expected).unwrap();
+
+ let export = export_json(
+ vec![Folder {
+ id: "942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap(),
+ name: "Important".to_string(),
+ }],
+ vec![
+ Cipher {
+ id: "25c8c414-b446-48e9-a1bd-b10700bbd740".parse().unwrap(),
+ folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()),
+
+ name: "Bitwarden".to_string(),
+ notes: Some("My note".to_string()),
+
+ r#type: CipherType::Login(Box::new(Login {
+ username: Some("test@bitwarden.com".to_string()),
+ password: Some("asdfasdfasdf".to_string()),
+ login_uris: vec![LoginUri {
+ uri: Some("https://vault.bitwarden.com".to_string()),
+ r#match: None,
+ }],
+ totp: Some("ABC".to_string()),
+ })),
+
+ favorite: true,
+ reprompt: 0,
+
+ fields: vec![
+ Field {
+ name: Some("Text".to_string()),
+ value: Some("A".to_string()),
+ r#type: 0,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Hidden".to_string()),
+ value: Some("B".to_string()),
+ r#type: 1,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Boolean (true)".to_string()),
+ value: Some("true".to_string()),
+ r#type: 2,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Boolean (false)".to_string()),
+ value: Some("false".to_string()),
+ r#type: 2,
+ linked_id: None,
+ },
+ Field {
+ name: Some("Linked".to_string()),
+ value: None,
+ r#type: 3,
+ linked_id: Some(101),
+ },
+ ],
+
+ revision_date: "2024-01-30T14:09:33.753Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:23:54.416Z".parse().unwrap(),
+ deleted_date: None,
+ },
+ Cipher {
+ id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(),
+ folder_id: None,
+
+ name: "My secure note".to_string(),
+ notes: Some("Very secure!".to_string()),
+
+ r#type: CipherType::SecureNote(Box::new(SecureNote {
+ r#type: SecureNoteType::Generic,
+ })),
+
+ favorite: false,
+ reprompt: 0,
+
+ fields: vec![],
+
+ revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(),
+ creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(),
+ deleted_date: None,
+ },
+ Cipher {
+ id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(),
+ folder_id: None,
+
+ name: "My card".to_string(),
+ notes: None,
+
+ r#type: CipherType::Card(Box::new(Card {
+ cardholder_name: Some("John Doe".to_string()),
+ exp_month: Some("1".to_string()),
+ exp_year: Some("2032".to_string()),
+ code: Some("123".to_string()),
+ brand: Some("Visa".to_string()),
+ number: Some("4111111111111111".to_string()),
+ })),
+
+ favorite: false,
+ reprompt: 0,
+
+ fields: vec![],
+
+ revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
+ creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
+ deleted_date: None,
+ },
+ Cipher {
+ id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(),
+ folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()),
+
+ name: "My identity".to_string(),
+ notes: None,
+
+ r#type: CipherType::Identity(Box::new(Identity {
+ title: Some("Mr".to_string()),
+ first_name: Some("John".to_string()),
+ middle_name: None,
+ last_name: Some("Doe".to_string()),
+ address1: None,
+ address2: None,
+ address3: None,
+ city: None,
+ state: None,
+ postal_code: None,
+ country: None,
+ company: Some("Bitwarden".to_string()),
+ email: None,
+ phone: None,
+ ssn: None,
+ username: Some("JDoe".to_string()),
+ passport_number: None,
+ license_number: None,
+ })),
+
+ favorite: false,
+ reprompt: 0,
+
+ fields: vec![],
+
+ revision_date: "2024-01-30T17:54:50.706Z".parse().unwrap(),
+ creation_date: "2024-01-30T17:54:50.706Z".parse().unwrap(),
+ deleted_date: None,
+ },
+ ],
+ )
+ .unwrap();
+
+ assert_eq!(
+ export.parse::().unwrap(),
+ expected.parse::().unwrap()
+ )
+ }
+}
diff --git a/crates/bitwarden-exporters/src/lib.rs b/crates/bitwarden-exporters/src/lib.rs
new file mode 100644
index 000000000..bb690fbc1
--- /dev/null
+++ b/crates/bitwarden-exporters/src/lib.rs
@@ -0,0 +1,142 @@
+use chrono::{DateTime, Utc};
+use thiserror::Error;
+use uuid::Uuid;
+
+mod csv;
+use csv::export_csv;
+mod json;
+use json::export_json;
+
+pub enum Format {
+ Csv,
+ Json,
+ EncryptedJson { password: String },
+}
+
+/// Export representation of a Bitwarden folder.
+///
+/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
+/// that is not tied to the internal vault models. We may revisit this in the future.
+pub struct Folder {
+ pub id: Uuid,
+ pub name: String,
+}
+
+/// Export representation of a Bitwarden cipher.
+///
+/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
+/// that is not tied to the internal vault models. We may revisit this in the future.
+pub struct Cipher {
+ pub id: Uuid,
+ pub folder_id: Option,
+
+ pub name: String,
+ pub notes: Option,
+
+ pub r#type: CipherType,
+
+ pub favorite: bool,
+ pub reprompt: u8,
+
+ pub fields: Vec,
+
+ pub revision_date: DateTime,
+ pub creation_date: DateTime,
+ pub deleted_date: Option>,
+}
+
+#[derive(Clone)]
+pub struct Field {
+ pub name: Option,
+ pub value: Option,
+ pub r#type: u8,
+ pub linked_id: Option,
+}
+
+pub enum CipherType {
+ Login(Box),
+ SecureNote(Box),
+ Card(Box),
+ Identity(Box),
+}
+
+impl ToString for CipherType {
+ fn to_string(&self) -> String {
+ match self {
+ CipherType::Login(_) => "login".to_string(),
+ CipherType::SecureNote(_) => "note".to_string(),
+ CipherType::Card(_) => "card".to_string(),
+ CipherType::Identity(_) => "identity".to_string(),
+ }
+ }
+}
+
+pub struct Login {
+ pub username: Option,
+ pub password: Option,
+ pub login_uris: Vec,
+ pub totp: Option,
+}
+
+pub struct LoginUri {
+ pub uri: Option,
+ pub r#match: Option,
+}
+
+pub struct Card {
+ pub cardholder_name: Option,
+ pub exp_month: Option,
+ pub exp_year: Option,
+ pub code: Option,
+ pub brand: Option,
+ pub number: Option,
+}
+
+pub struct SecureNote {
+ pub r#type: SecureNoteType,
+}
+
+pub enum SecureNoteType {
+ Generic = 0,
+}
+
+pub struct Identity {
+ pub title: Option,
+ pub first_name: Option,
+ pub middle_name: Option,
+ pub last_name: Option,
+ pub address1: Option,
+ pub address2: Option,
+ pub address3: Option,
+ pub city: Option,
+ pub state: Option,
+ pub postal_code: Option,
+ pub country: Option,
+ pub company: Option,
+ pub email: Option,
+ pub phone: Option,
+ pub ssn: Option,
+ pub username: Option,
+ pub passport_number: Option,
+ pub license_number: Option,
+}
+
+#[derive(Error, Debug)]
+pub enum ExportError {
+ #[error("CSV error: {0}")]
+ Csv(#[from] csv::CsvError),
+ #[error("JSON error: {0}")]
+ Json(#[from] json::JsonError),
+}
+
+pub fn export(
+ folders: Vec,
+ ciphers: Vec,
+ format: Format,
+) -> Result {
+ match format {
+ Format::Csv => Ok(export_csv(folders, ciphers)?),
+ Format::Json => Ok(export_json(folders, ciphers)?),
+ Format::EncryptedJson { password: _ } => todo!(),
+ }
+}
diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml
index d9508258b..c6f580282 100644
--- a/crates/bitwarden/Cargo.toml
+++ b/crates/bitwarden/Cargo.toml
@@ -31,6 +31,7 @@ base64 = ">=0.21.2, <0.22"
bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.3" }
bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.3" }
bitwarden-crypto = { path = "../bitwarden-crypto", version = "=0.1.0" }
+bitwarden-exporters = { path = "../bitwarden-exporters", version = "0.1.0" }
bitwarden-generators = { path = "../bitwarden-generators", version = "0.1.0" }
chrono = { version = ">=0.4.26, <0.5", features = [
"clock",
diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs
index 173557b04..ed5d27c3e 100644
--- a/crates/bitwarden/src/error.rs
+++ b/crates/bitwarden/src/error.rs
@@ -4,6 +4,7 @@ use std::{borrow::Cow, fmt::Debug};
use bitwarden_api_api::apis::Error as ApiError;
use bitwarden_api_identity::apis::Error as IdentityError;
+use bitwarden_exporters::ExportError;
use bitwarden_generators::{PassphraseError, PasswordError, UsernameError};
use reqwest::StatusCode;
use thiserror::Error;
@@ -50,6 +51,7 @@ pub enum Error {
#[error("The state file could not be read")]
InvalidStateFile,
+ // Generators
#[error(transparent)]
UsernameError(#[from] UsernameError),
#[error(transparent)]
@@ -57,6 +59,9 @@ pub enum Error {
#[error(transparent)]
PasswordError(#[from] PasswordError),
+ #[error(transparent)]
+ ExportError(#[from] ExportError),
+
#[error("Internal error: {0}")]
Internal(Cow<'static, str>),
}
diff --git a/crates/bitwarden/src/tool/exporters/client_exporter.rs b/crates/bitwarden/src/tool/exporters/client_exporter.rs
index 9e0dfd5fc..05eb737f3 100644
--- a/crates/bitwarden/src/tool/exporters/client_exporter.rs
+++ b/crates/bitwarden/src/tool/exporters/client_exporter.rs
@@ -6,7 +6,7 @@ use crate::{
};
pub struct ClientExporters<'a> {
- pub(crate) _client: &'a crate::Client,
+ pub(crate) client: &'a crate::Client,
}
impl<'a> ClientExporters<'a> {
@@ -17,7 +17,7 @@ impl<'a> ClientExporters<'a> {
ciphers: Vec,
format: ExportFormat,
) -> Result {
- export_vault(folders, ciphers, format)
+ export_vault(self.client, folders, ciphers, format)
}
pub async fn export_organization_vault(
@@ -32,6 +32,6 @@ impl<'a> ClientExporters<'a> {
impl<'a> Client {
pub fn exporters(&'a self) -> ClientExporters<'a> {
- ClientExporters { _client: self }
+ ClientExporters { client: self }
}
}
diff --git a/crates/bitwarden/src/tool/exporters/mod.rs b/crates/bitwarden/src/tool/exporters/mod.rs
index d03ddeb77..cbdb5bb86 100644
--- a/crates/bitwarden/src/tool/exporters/mod.rs
+++ b/crates/bitwarden/src/tool/exporters/mod.rs
@@ -1,8 +1,14 @@
+use bitwarden_crypto::Decryptable;
+use bitwarden_exporters::export;
use schemars::JsonSchema;
use crate::{
- error::Result,
- vault::{Cipher, Collection, Folder},
+ error::{Error, Result},
+ vault::{
+ login::LoginUriView, Cipher, CipherType, CipherView, Collection, FieldView, Folder,
+ FolderView, SecureNoteType,
+ },
+ Client,
};
mod client_exporter;
@@ -13,21 +19,26 @@ pub use client_exporter::ClientExporters;
pub enum ExportFormat {
Csv,
Json,
- AccountEncryptedJson, // TODO: Should we deprecate this option completely?
EncryptedJson { password: String },
}
pub(super) fn export_vault(
- _folders: Vec,
- _ciphers: Vec,
+ client: &Client,
+ folders: Vec,
+ ciphers: Vec,
format: ExportFormat,
) -> Result {
- Ok(match format {
- ExportFormat::Csv => "Csv".to_owned(),
- ExportFormat::Json => "Json".to_owned(),
- ExportFormat::AccountEncryptedJson => "AccountEncryptedJson".to_owned(),
- ExportFormat::EncryptedJson { .. } => "EncryptedJson".to_owned(),
- })
+ let enc = client.get_encryption_settings()?;
+
+ let folders: Vec = folders.decrypt(enc, &None)?;
+ let folders: Vec =
+ folders.into_iter().flat_map(|f| f.try_into()).collect();
+
+ let ciphers: Vec = ciphers.decrypt(enc, &None)?;
+ let ciphers: Vec =
+ ciphers.into_iter().flat_map(|c| c.try_into()).collect();
+
+ Ok(export(folders, ciphers, format.into())?)
}
pub(super) fn export_organization_vault(
@@ -37,3 +48,248 @@ pub(super) fn export_organization_vault(
) -> Result {
todo!();
}
+
+impl TryFrom for bitwarden_exporters::Folder {
+ type Error = Error;
+
+ fn try_from(value: FolderView) -> Result {
+ Ok(Self {
+ id: value.id.ok_or(Error::MissingFields)?,
+ name: value.name,
+ })
+ }
+}
+
+impl TryFrom for bitwarden_exporters::Cipher {
+ type Error = Error;
+
+ fn try_from(value: CipherView) -> Result {
+ let r = match value.r#type {
+ CipherType::Login => {
+ let l = value.login.ok_or(Error::MissingFields)?;
+ bitwarden_exporters::CipherType::Login(Box::new(bitwarden_exporters::Login {
+ username: l.username,
+ password: l.password,
+ login_uris: l
+ .uris
+ .unwrap_or_default()
+ .into_iter()
+ .map(|u| u.into())
+ .collect(),
+ totp: l.totp,
+ }))
+ }
+ CipherType::SecureNote => bitwarden_exporters::CipherType::SecureNote(Box::new(
+ bitwarden_exporters::SecureNote {
+ r#type: value
+ .secure_note
+ .map(|t| t.r#type)
+ .unwrap_or(SecureNoteType::Generic)
+ .into(),
+ },
+ )),
+ CipherType::Card => {
+ let c = value.card.ok_or(Error::MissingFields)?;
+ bitwarden_exporters::CipherType::Card(Box::new(bitwarden_exporters::Card {
+ cardholder_name: c.cardholder_name,
+ exp_month: c.exp_month,
+ exp_year: c.exp_year,
+ code: c.code,
+ brand: c.brand,
+ number: c.number,
+ }))
+ }
+ CipherType::Identity => {
+ let i = value.identity.ok_or(Error::MissingFields)?;
+ bitwarden_exporters::CipherType::Identity(Box::new(bitwarden_exporters::Identity {
+ title: i.title,
+ first_name: i.first_name,
+ middle_name: i.middle_name,
+ last_name: i.last_name,
+ address1: i.address1,
+ address2: i.address2,
+ address3: i.address3,
+ city: i.city,
+ state: i.state,
+ postal_code: i.postal_code,
+ country: i.country,
+ company: i.company,
+ email: i.email,
+ phone: i.phone,
+ ssn: i.ssn,
+ username: i.username,
+ passport_number: i.passport_number,
+ license_number: i.license_number,
+ }))
+ }
+ };
+
+ Ok(Self {
+ id: value.id.ok_or(Error::MissingFields)?,
+ folder_id: value.folder_id,
+ name: value.name,
+ notes: value.notes,
+ r#type: r,
+ favorite: value.favorite,
+ reprompt: value.reprompt as u8,
+ fields: value
+ .fields
+ .unwrap_or_default()
+ .into_iter()
+ .map(|f| f.into())
+ .collect(),
+ revision_date: value.revision_date,
+ creation_date: value.creation_date,
+ deleted_date: value.deleted_date,
+ })
+ }
+}
+
+impl From for bitwarden_exporters::Field {
+ fn from(value: FieldView) -> Self {
+ Self {
+ name: value.name,
+ value: value.value,
+ r#type: value.r#type as u8,
+ linked_id: value.linked_id.map(|id| id.into()),
+ }
+ }
+}
+
+impl From for bitwarden_exporters::LoginUri {
+ fn from(value: LoginUriView) -> Self {
+ Self {
+ r#match: value.r#match.map(|v| v as u8),
+ uri: value.uri,
+ }
+ }
+}
+
+impl From for bitwarden_exporters::SecureNoteType {
+ fn from(value: SecureNoteType) -> Self {
+ match value {
+ SecureNoteType::Generic => bitwarden_exporters::SecureNoteType::Generic,
+ }
+ }
+}
+
+impl From for bitwarden_exporters::Format {
+ fn from(value: ExportFormat) -> Self {
+ match value {
+ ExportFormat::Csv => Self::Csv,
+ ExportFormat::Json => Self::Json,
+ ExportFormat::EncryptedJson { password } => Self::EncryptedJson { password },
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use chrono::{DateTime, Utc};
+
+ use super::*;
+ use crate::vault::{login::LoginView, CipherRepromptType};
+
+ #[test]
+ fn test_try_from_folder_view() {
+ let view = FolderView {
+ id: Some("fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap()),
+ name: "test_name".to_string(),
+ revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
+ };
+
+ let f: bitwarden_exporters::Folder = view.try_into().unwrap();
+
+ assert_eq!(
+ f.id,
+ "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap()
+ );
+ assert_eq!(f.name, "test_name".to_string());
+ }
+
+ #[test]
+ fn test_try_from_cipher_view_login() {
+ let cipher_view = CipherView {
+ r#type: CipherType::Login,
+ login: Some(LoginView {
+ username: Some("test_username".to_string()),
+ password: Some("test_password".to_string()),
+ password_revision_date: None,
+ uris: None,
+ totp: None,
+ autofill_on_page_load: None,
+ }),
+ id: "fd411a1a-fec8-4070-985d-0e6560860e69".parse().ok(),
+ organization_id: None,
+ folder_id: None,
+ collection_ids: vec![],
+ key: None,
+ name: "My login".to_string(),
+ notes: None,
+ identity: None,
+ card: None,
+ secure_note: None,
+ favorite: false,
+ reprompt: CipherRepromptType::None,
+ organization_use_totp: true,
+ edit: true,
+ view_password: true,
+ local_data: None,
+ attachments: None,
+ fields: None,
+ password_history: None,
+ creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
+ deleted_date: None,
+ revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
+ };
+
+ let cipher: bitwarden_exporters::Cipher = cipher_view.try_into().unwrap();
+
+ assert_eq!(
+ cipher.id,
+ "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap()
+ );
+ assert_eq!(cipher.folder_id, None);
+ assert_eq!(cipher.name, "My login".to_string());
+ assert_eq!(cipher.notes, None);
+ assert!(!cipher.favorite);
+ assert_eq!(cipher.reprompt, 0);
+ assert!(cipher.fields.is_empty());
+ assert_eq!(
+ cipher.revision_date,
+ "2024-01-30T17:55:36.150Z".parse::>().unwrap()
+ );
+ assert_eq!(
+ cipher.creation_date,
+ "2024-01-30T17:55:36.150Z".parse::>().unwrap()
+ );
+ assert_eq!(cipher.deleted_date, None);
+
+ if let bitwarden_exporters::CipherType::Login(l) = cipher.r#type {
+ assert_eq!(l.username, Some("test_username".to_string()));
+ assert_eq!(l.password, Some("test_password".to_string()));
+ assert!(l.login_uris.is_empty());
+ assert_eq!(l.totp, None);
+ } else {
+ panic!("Expected login type");
+ }
+ }
+
+ #[test]
+ fn test_from_export_format() {
+ assert!(matches!(
+ bitwarden_exporters::Format::from(ExportFormat::Csv),
+ bitwarden_exporters::Format::Csv
+ ));
+ assert!(matches!(
+ bitwarden_exporters::Format::from(ExportFormat::Json),
+ bitwarden_exporters::Format::Json
+ ));
+ assert!(matches!(
+ bitwarden_exporters::Format::from(ExportFormat::EncryptedJson {
+ password: "password".to_string()
+ }),
+ bitwarden_exporters::Format::EncryptedJson { .. }
+ ));
+ }
+}
diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden/src/vault/cipher/field.rs
index db896474f..bde713a05 100644
--- a/crates/bitwarden/src/vault/cipher/field.rs
+++ b/crates/bitwarden/src/vault/cipher/field.rs
@@ -34,11 +34,11 @@ pub struct Field {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "mobile", derive(uniffi::Record))]
pub struct FieldView {
- name: Option,
- value: Option,
- r#type: FieldType,
+ pub(crate) name: Option,
+ pub(crate) value: Option,
+ pub(crate) r#type: FieldType,
- linked_id: Option,
+ pub(crate) linked_id: Option,
}
impl KeyEncryptable for FieldView {
diff --git a/crates/bitwarden/src/vault/cipher/linked_id.rs b/crates/bitwarden/src/vault/cipher/linked_id.rs
index 6fb676dfe..77429438e 100644
--- a/crates/bitwarden/src/vault/cipher/linked_id.rs
+++ b/crates/bitwarden/src/vault/cipher/linked_id.rs
@@ -25,7 +25,13 @@ impl UniffiCustomTypeConverter for LinkedIdType {
}
fn from_custom(obj: Self) -> Self::Builtin {
- serde_json::to_value(obj)
+ obj.into()
+ }
+}
+
+impl From for u32 {
+ fn from(v: LinkedIdType) -> Self {
+ serde_json::to_value(v)
.expect("LinkedIdType should be serializable")
.as_u64()
.expect("Not a numeric enum value") as u32
diff --git a/crates/bitwarden/src/vault/cipher/mod.rs b/crates/bitwarden/src/vault/cipher/mod.rs
index c891f439d..c2b49eb37 100644
--- a/crates/bitwarden/src/vault/cipher/mod.rs
+++ b/crates/bitwarden/src/vault/cipher/mod.rs
@@ -9,4 +9,9 @@ pub(crate) mod local_data;
pub(crate) mod login;
pub(crate) mod secure_note;
-pub use cipher::{Cipher, CipherListView, CipherView};
+pub use attachment::{
+ Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView,
+};
+pub use cipher::{Cipher, CipherListView, CipherRepromptType, CipherType, CipherView};
+pub use field::FieldView;
+pub use secure_note::SecureNoteType;
diff --git a/crates/bitwarden/src/vault/cipher/secure_note.rs b/crates/bitwarden/src/vault/cipher/secure_note.rs
index 8f7069ee1..2433a9c2a 100644
--- a/crates/bitwarden/src/vault/cipher/secure_note.rs
+++ b/crates/bitwarden/src/vault/cipher/secure_note.rs
@@ -24,7 +24,7 @@ pub struct SecureNote {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "mobile", derive(uniffi::Record))]
pub struct SecureNoteView {
- r#type: SecureNoteType,
+ pub(crate) r#type: SecureNoteType,
}
impl KeyEncryptable for SecureNoteView {
diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs
index 17d1d40aa..edd1cac42 100644
--- a/crates/bitwarden/src/vault/folder.rs
+++ b/crates/bitwarden/src/vault/folder.rs
@@ -22,9 +22,9 @@ pub struct Folder {
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "mobile", derive(uniffi::Record))]
pub struct FolderView {
- id: Option,
- name: String,
- revision_date: DateTime,
+ pub id: Option,
+ pub name: String,
+ pub revision_date: DateTime,
}
impl LocateKey for FolderView {}
diff --git a/crates/bitwarden/src/vault/mod.rs b/crates/bitwarden/src/vault/mod.rs
index dce7b0e04..2addfec6b 100644
--- a/crates/bitwarden/src/vault/mod.rs
+++ b/crates/bitwarden/src/vault/mod.rs
@@ -6,12 +6,7 @@ mod send;
#[cfg(feature = "mobile")]
mod totp;
-pub use cipher::{
- attachment::{
- Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView,
- },
- Cipher, CipherListView, CipherView,
-};
+pub use cipher::*;
pub use collection::{Collection, CollectionView};
pub use folder::{Folder, FolderView};
pub use password_history::{PasswordHistory, PasswordHistoryView};
From 1595306d0f80807983c374aaebc4938a835be78c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?=
Date: Tue, 6 Feb 2024 18:02:33 +0100
Subject: [PATCH 34/34] [DEVOPS-1711] Add Docker image for bws versioning for
release workflow (#573)
## Type of change
```
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [x] Build/deploy pipeline (DevOps)
- [ ] Other
```
## Objective
Add docker publish to `release-cli.yml` workflow.
## Code changes
- **.github/workflows/release-cli.yml:** Add docker publish job to
`release-cli.yml` workflow.
- **.github/workflows/build-cli-docker.yml** Remove unused input.
Publish docker only if building for publish branch
## Before you submit
- Please add **unit tests** where it makes sense to do so
---------
Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
---
.github/workflows/build-cli-docker.yml | 7 +--
.github/workflows/release-cli.yml | 85 ++++++++++++++++++++++++--
2 files changed, 82 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/build-cli-docker.yml b/.github/workflows/build-cli-docker.yml
index c0aa62664..5cee3899b 100644
--- a/.github/workflows/build-cli-docker.yml
+++ b/.github/workflows/build-cli-docker.yml
@@ -6,11 +6,6 @@ on:
paths:
- "crates/bws/**"
workflow_dispatch:
- inputs:
- sdk_branch:
- description: "Server branch name to deploy (examples: 'master', 'rc', 'feature/sm')"
- type: string
- default: master
pull_request:
paths:
- ".github/workflows/build-cli-docker.yml"
@@ -111,7 +106,7 @@ jobs:
platforms: |
linux/amd64,
linux/arm64/v8
- push: true
+ push: ${{ env.is_publish_branch }}
tags: ${{ steps.tag-list.outputs.tags }}
secrets: |
"GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}"
diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml
index a6059a1d5..fa1ffc346 100644
--- a/.github/workflows/release-cli.yml
+++ b/.github/workflows/release-cli.yml
@@ -8,17 +8,19 @@ on:
release_type:
description: "Release Options"
required: true
- default: "Initial Release"
+ default: "Release"
type: choice
options:
- - Initial Release
- - Redeploy
+ - Release
- Dry Run
defaults:
run:
shell: bash
+env:
+ _AZ_REGISTRY: bitwardenprod.azurecr.io
+
jobs:
setup:
name: Setup
@@ -120,7 +122,7 @@ jobs:
publish:
name: Publish bws to crates.io
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
needs:
- setup
steps:
@@ -156,3 +158,78 @@ jobs:
PUBLISH_GRACE_SLEEP: 10
CARGO_REGISTRY_TOKEN: ${{ steps.retrieve-secrets.outputs.cratesio-api-token }}
run: cargo-release release publish -p bws --execute --no-confirm
+
+ publish-docker:
+ name: Publish docker versioned and latest image
+ runs-on: ubuntu-22.04
+ needs: setup
+ steps:
+ - name: Checkout
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+
+ - name: Generate tag list
+ id: tag-list
+ env:
+ VERSION: ${{ needs.setup.outputs.release-version }}
+ DRY_RUN: ${{ inputs.release_type == 'Dry Run' }}
+ run: |
+ if [[ "${DRY_RUN}" == "true" ]]; then
+ REF=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
+ IMAGE_TAG=$(echo "${REF}" | sed "s#/#-#g") # slash safe branch name
+ echo "tags=$_AZ_REGISTRY/bws:${IMAGE_TAG},bitwarden/bws:${IMAGE_TAG}" >> $GITHUB_OUTPUT
+ else
+ echo "tags=$_AZ_REGISTRY/bws:${VERSION},bitwarden/bws:${VERSION},$_AZ_REGISTRY/bws:latest,bitwarden/bws:latest" >> $GITHUB_OUTPUT
+ fi
+
+ ########## Set up Docker ##########
+ - name: Set up QEMU emulators
+ uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
+
+ ########## Login to Docker registries ##########
+ - name: Login to Azure - Prod Subscription
+ uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
+ with:
+ creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
+
+ - name: Login to Azure ACR
+ run: az acr login -n ${_AZ_REGISTRY%.azurecr.io}
+
+ - name: Login to Azure - CI Subscription
+ uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
+ with:
+ creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
+
+ - name: Retrieve github PAT secrets
+ id: retrieve-secret-pat
+ uses: bitwarden/gh-actions/get-keyvault-secrets@main
+ with:
+ keyvault: "bitwarden-ci"
+ secrets: "github-pat-bitwarden-devops-bot-repo-scope"
+
+ - name: Setup Docker Trust
+ uses: bitwarden/gh-actions/setup-docker-trust@main
+ with:
+ azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
+ azure-keyvault-name: "bitwarden-ci"
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
+ with:
+ context: .
+ file: crates/bws/Dockerfile
+ platforms: |
+ linux/amd64,
+ linux/arm64/v8
+ push: ${{ inputs.release_type != 'Dry Run' }}
+ tags: ${{ steps.tag-list.outputs.tags }}
+ secrets: |
+ "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}"
+
+ - name: Log out of Docker and disable Docker Notary
+ if: ${{ github.event.inputs.release_type != 'Dry Run' }}
+ run: |
+ docker logout
+ echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV