Skip to content

Commit

Permalink
Update exporters to use Sensitive
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Mar 7, 2024
1 parent cb5f43a commit ba14e8c
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 204 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

14 changes: 13 additions & 1 deletion crates/bitwarden-crypto/src/sensitive/sensitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ impl<V: Zeroize + JsonSchema> JsonSchema for Sensitive<V> {
}
}

impl Sensitive<String> {
// We use a lot of `&str` in our tests, so we expose this helper
// to make it easier.
// IMPORTANT: This should not be used outside of test code
// Note that we can't just mark it with #[cfg(test)] because that only applies
// when testing this crate, not when testing other crates that depend on it.
// By at least limiting it to &'static str we should be able to avoid accidental usages
pub fn test(value: &'static str) -> Self {
Self::new(Box::new(value.to_string()))
}
}

#[cfg(test)]
mod tests {
use schemars::schema_for;
Expand All @@ -109,7 +121,7 @@ mod tests {

#[test]
fn test_debug() {
let string = Sensitive::new(Box::new("test".to_string()));
let string = Sensitive::test("test");
assert_eq!(
format!("{:?}", string),
"Sensitive { type: \"alloc::string::String\", value: \"********\" }"
Expand Down
1 change: 1 addition & 0 deletions crates/bitwarden-exporters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ chrono = { version = ">=0.4.26, <0.5", features = [
"std",
], default-features = false }
csv = "1.3.0"
itertools = "0.12.1"
serde = { version = ">=1.0, <2.0", features = ["derive"] }
serde_json = ">=1.0.96, <2.0"
thiserror = ">=1.0.40, <2.0"
Expand Down
69 changes: 39 additions & 30 deletions crates/bitwarden-exporters/src/csv.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::collections::HashMap;

use bitwarden_crypto::{DecryptedString, Sensitive};
use csv::Writer;
use serde::Serializer;
use thiserror::Error;
use uuid::Uuid;

use crate::{Cipher, CipherType, Field, Folder};

Expand All @@ -14,7 +14,7 @@ pub enum CsvError {
}

pub(crate) fn export_csv(folders: Vec<Folder>, ciphers: Vec<Cipher>) -> Result<String, CsvError> {
let folders: HashMap<Uuid, String> = folders.into_iter().map(|f| (f.id, f.name)).collect();
let folders: HashMap<_, _> = folders.into_iter().map(|f| (f.id, f.name)).collect();

let rows = ciphers
.into_iter()
Expand Down Expand Up @@ -59,27 +59,30 @@ pub(crate) fn export_csv(folders: Vec<Folder>, ciphers: Vec<Cipher>) -> Result<S
/// Be careful when changing this struct to maintain compatibility with old exports.
#[derive(serde::Serialize)]
struct CsvRow {
folder: Option<String>,
folder: Option<DecryptedString>,
#[serde(serialize_with = "bool_serialize")]
favorite: bool,
r#type: String,
name: String,
notes: Option<String>,
name: DecryptedString,
notes: Option<DecryptedString>,
#[serde(serialize_with = "fields_serialize")]
fields: Vec<Field>,
reprompt: u8,
#[serde(serialize_with = "vec_serialize")]
login_uri: Vec<String>,
login_username: Option<String>,
login_password: Option<String>,
login_totp: Option<String>,
login_uri: Vec<DecryptedString>,
login_username: Option<DecryptedString>,
login_password: Option<DecryptedString>,
login_totp: Option<DecryptedString>,
}

fn vec_serialize<S>(x: &[String], s: S) -> Result<S::Ok, S::Error>
fn vec_serialize<S>(x: &[DecryptedString], s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(x.join(",").as_str())
let iter = itertools::Itertools::intersperse(x.iter().map(|s| s.expose().as_str()), ",");
let result: Sensitive<String> = Sensitive::new(Box::new(iter.collect()));

s.serialize_str(result.expose())
}

fn bool_serialize<S>(x: &bool, s: S) -> Result<S::Ok, S::Error>
Expand All @@ -98,8 +101,14 @@ where
.map(|f| {
format!(
"{}: {}",
f.name.to_owned().unwrap_or_default(),
f.value.to_owned().unwrap_or_default()
f.name
.as_ref()
.map(|n| n.expose().to_owned())
.unwrap_or_default(),
f.value
.as_ref()
.map(|n| n.expose().to_owned())
.unwrap_or_default(),
)
})
.collect::<Vec<String>>()
Expand All @@ -118,24 +127,24 @@ mod tests {
let folders = vec![
Folder {
id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
name: "Test Folder A".to_string(),
name: DecryptedString::test("Test Folder A"),
},
Folder {
id: "583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap(),
name: "Test Folder B".to_string(),
name: DecryptedString::test("Test Folder B"),
},
];
let ciphers = vec![
Cipher {
id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
folder_id: None,
name: "[email protected]".to_string(),
name: DecryptedString::test("[email protected]"),
notes: None,
r#type: CipherType::Login(Box::new(Login {
username: Some("[email protected]".to_string()),
password: Some("Abc123".to_string()),
username: Some(DecryptedString::test("[email protected]")),
password: Some(DecryptedString::test("Abc123")),
login_uris: vec![LoginUri {
uri: Some("https://google.com".to_string()),
uri: Some(DecryptedString::test("https://google.com")),
r#match: None,
}],
totp: None,
Expand All @@ -150,29 +159,29 @@ mod tests {
Cipher {
id: "7dd81bd0-cc72-4f42-96e7-b0fc014e71a3".parse().unwrap(),
folder_id: Some("583e7665-0126-4d37-9139-b0d20184dd86".parse().unwrap()),
name: "Steam Account".to_string(),
name: DecryptedString::test("Steam Account"),
notes: None,
r#type: CipherType::Login(Box::new(Login {
username: Some("steam".to_string()),
password: Some("3Pvb8u7EfbV*nJ".to_string()),
username: Some(DecryptedString::test("steam")),
password: Some(DecryptedString::test("3Pvb8u7EfbV*nJ")),
login_uris: vec![LoginUri {
uri: Some("https://steampowered.com".to_string()),
uri: Some(DecryptedString::test("https://steampowered.com")),
r#match: None,
}],
totp: Some("steam://ABCD123".to_string()),
totp: Some(DecryptedString::test("steam://ABCD123")),
})),
favorite: true,
reprompt: 0,
fields: vec![
Field {
name: Some("Test".to_string()),
value: Some("v".to_string()),
name: Some(DecryptedString::test("Test")),
value: Some(DecryptedString::test("v")),
r#type: 0,
linked_id: None,
},
Field {
name: Some("Hidden".to_string()),
value: Some("asdfer".to_string()),
name: Some(DecryptedString::test("Hidden")),
value: Some(DecryptedString::test("asdfer")),
r#type: 1,
linked_id: None,
},
Expand Down Expand Up @@ -200,7 +209,7 @@ mod tests {
let ciphers = vec![Cipher {
id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
folder_id: None,
name: "My Card".to_string(),
name: DecryptedString::test("My Card"),
notes: None,
r#type: CipherType::Card(Box::new(Card {
cardholder_name: None,
Expand Down Expand Up @@ -229,7 +238,7 @@ mod tests {
let ciphers = vec![Cipher {
id: "d55d65d7-c161-40a4-94ca-b0d20184d91a".parse().unwrap(),
folder_id: None,
name: "My Identity".to_string(),
name: DecryptedString::test("My Identity"),
notes: None,
r#type: CipherType::Identity(Box::new(Identity {
title: None,
Expand Down
64 changes: 33 additions & 31 deletions crates/bitwarden-exporters/src/encrypted_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub(crate) struct EncryptedJsonExport {
mod tests {
use std::num::NonZeroU32;

use bitwarden_crypto::DecryptedString;

use super::*;
use crate::{
Card, Cipher, CipherType, Field, Identity, Login, LoginUri, SecureNote, SecureNoteType,
Expand All @@ -93,56 +95,56 @@ mod tests {
let _export = export_encrypted_json(
vec![Folder {
id: "942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap(),
name: "Important".to_string(),
name: DecryptedString::test("Important"),
}],
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()),
name: DecryptedString::test("Bitwarden"),
notes: Some(DecryptedString::test("My note")),

r#type: CipherType::Login(Box::new(Login {
username: Some("[email protected]".to_string()),
password: Some("asdfasdfasdf".to_string()),
username: Some(DecryptedString::test("[email protected]")),
password: Some(DecryptedString::test("asdfasdfasdf")),
login_uris: vec![LoginUri {
uri: Some("https://vault.bitwarden.com".to_string()),
uri: Some(DecryptedString::test("https://vault.bitwarden.com")),
r#match: None,
}],
totp: Some("ABC".to_string()),
totp: Some(DecryptedString::test("ABC")),
})),

favorite: true,
reprompt: 0,

fields: vec![
Field {
name: Some("Text".to_string()),
value: Some("A".to_string()),
name: Some(DecryptedString::test("Text")),
value: Some(DecryptedString::test("A")),
r#type: 0,
linked_id: None,
},
Field {
name: Some("Hidden".to_string()),
value: Some("B".to_string()),
name: Some(DecryptedString::test("Hidden")),
value: Some(DecryptedString::test("B")),
r#type: 1,
linked_id: None,
},
Field {
name: Some("Boolean (true)".to_string()),
value: Some("true".to_string()),
name: Some(DecryptedString::test("Boolean (true)")),
value: Some(DecryptedString::test("true")),
r#type: 2,
linked_id: None,
},
Field {
name: Some("Boolean (false)".to_string()),
value: Some("false".to_string()),
name: Some(DecryptedString::test("Boolean (false)")),
value: Some(DecryptedString::test("false")),
r#type: 2,
linked_id: None,
},
Field {
name: Some("Linked".to_string()),
name: Some(DecryptedString::test("Linked")),
value: None,
r#type: 3,
linked_id: Some(101),
Expand All @@ -157,8 +159,8 @@ mod tests {
id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(),
folder_id: None,

name: "My secure note".to_string(),
notes: Some("Very secure!".to_string()),
name: DecryptedString::test("My secure note"),
notes: Some(DecryptedString::test("Very secure!")),

r#type: CipherType::SecureNote(Box::new(SecureNote {
r#type: SecureNoteType::Generic,
Expand All @@ -177,16 +179,16 @@ mod tests {
id: "3ed8de45-48ee-4e26-a2dc-b10701276c53".parse().unwrap(),
folder_id: None,

name: "My card".to_string(),
name: DecryptedString::test("My card"),
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()),
cardholder_name: Some(DecryptedString::test("John Doe")),
exp_month: Some(DecryptedString::test("1")),
exp_year: Some(DecryptedString::test("2032")),
code: Some(DecryptedString::test("123")),
brand: Some(DecryptedString::test("Visa")),
number: Some(DecryptedString::test("4111111111111111")),
})),

favorite: false,
Expand All @@ -202,26 +204,26 @@ mod tests {
id: "41cc3bc1-c3d9-4637-876c-b10701273712".parse().unwrap(),
folder_id: Some("942e2984-1b9a-453b-b039-b107012713b9".parse().unwrap()),

name: "My identity".to_string(),
name: DecryptedString::test("My identity"),
notes: None,

r#type: CipherType::Identity(Box::new(Identity {
title: Some("Mr".to_string()),
first_name: Some("John".to_string()),
title: Some(DecryptedString::test("Mr")),
first_name: Some(DecryptedString::test("John")),
middle_name: None,
last_name: Some("Doe".to_string()),
last_name: Some(DecryptedString::test("Doe")),
address1: None,
address2: None,
address3: None,
city: None,
state: None,
postal_code: None,
country: None,
company: Some("Bitwarden".to_string()),
company: Some(DecryptedString::test("Bitwarden")),
email: None,
phone: None,
ssn: None,
username: Some("JDoe".to_string()),
username: Some(DecryptedString::test("JDoe")),
passport_number: None,
license_number: None,
})),
Expand Down
Loading

0 comments on commit ba14e8c

Please sign in to comment.