-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
360 additions
and
17 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[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" | ||
|
||
[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" | ||
uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
use std::collections::HashMap; | ||
|
||
use csv::Writer; | ||
use serde::Serializer; | ||
use uuid::Uuid; | ||
|
||
use crate::{Cipher, CipherType, Field, Folder}; | ||
|
||
pub(crate) fn export_csv(folders: Vec<Folder>, ciphers: Vec<Cipher>) -> Result<String, String> { | ||
let folders: HashMap<Uuid, String> = folders.iter().map(|f| (f.id, f.name.clone())).collect(); | ||
|
||
let rows = ciphers.iter().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.clone(), | ||
reprompt: c.reprompt, | ||
login_uri: login.map(|l| l.login_uris.clone()).unwrap_or_default(), | ||
login_username: login.map(|l| l.username.clone()), | ||
login_password: login.map(|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(|_| "Failed to write CSV".to_string())?, | ||
) | ||
.map_err(|_| "Failed to convert CSV to UTF-8".to_string()) | ||
} | ||
|
||
#[derive(serde::Serialize)] | ||
struct CsvRow { | ||
folder: Option<String>, | ||
#[serde(serialize_with = "bool_serialize")] | ||
favorite: bool, | ||
r#type: String, | ||
name: String, | ||
notes: Option<String>, | ||
#[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>, | ||
} | ||
|
||
fn vec_serialize<S>(x: &[String], s: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
s.serialize_str(x.join(",").as_str()) | ||
} | ||
|
||
fn bool_serialize<S>(x: &bool, s: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
s.serialize_str(if *x { "1" } else { "" }) | ||
} | ||
|
||
fn fields_serialize<S>(x: &[Field], s: S) -> Result<S::Ok, S::Error> | ||
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::<Vec<String>>() | ||
.join("\n") | ||
.as_str(), | ||
) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::CipherLogin; | ||
|
||
#[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: "[email protected]".to_string(), | ||
notes: None, | ||
r#type: CipherType::Login(CipherLogin { | ||
username: "[email protected]".to_string(), | ||
password: "Abc123".to_string(), | ||
login_uris: vec!["https://google.com".to_string()], | ||
totp: None, | ||
}), | ||
favorite: false, | ||
reprompt: 0, | ||
fields: vec![], | ||
}, | ||
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(CipherLogin { | ||
username: "steam".to_string(), | ||
password: "3Pvb8u7EfbV*nJ".to_string(), | ||
login_uris: vec!["https://steampowered.com".to_string()], | ||
totp: Some("steam://ABCD123".to_string()), | ||
}), | ||
favorite: true, | ||
reprompt: 0, | ||
fields: vec![ | ||
Field { | ||
name: Some("Test".to_string()), | ||
value: Some("v".to_string()), | ||
}, | ||
Field { | ||
name: Some("Hidden".to_string()), | ||
value: Some("asdfer".to_string()), | ||
}, | ||
], | ||
}, | ||
]; | ||
|
||
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,[email protected],,,0,https://google.com,[email protected],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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
use crate::{Cipher, Folder}; | ||
|
||
pub(crate) fn export_json(folders: Vec<Folder>, ciphers: Vec<Cipher>) -> Result<String, String> { | ||
Ok("".to_owned()) | ||
} | ||
|
||
struct JsonExport { | ||
encrypted: bool, | ||
folders: Vec<Folder>, | ||
ciphers: Vec<Cipher>, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use chrono::{DateTime, Utc}; | ||
use uuid::Uuid; | ||
|
||
mod csv; | ||
use csv::export_csv; | ||
mod json; | ||
use json::export_json; | ||
|
||
pub enum Format { | ||
Csv, | ||
Json, | ||
EncryptedJson { password: String }, | ||
} | ||
|
||
pub struct Folder { | ||
pub id: Uuid, | ||
pub name: String, | ||
} | ||
|
||
pub struct Cipher { | ||
pub id: Uuid, | ||
pub folder_id: Option<Uuid>, | ||
|
||
pub name: String, | ||
pub notes: Option<String>, | ||
|
||
pub r#type: CipherType, | ||
|
||
pub favorite: bool, | ||
pub reprompt: u8, | ||
|
||
pub fields: Vec<Field>, | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct Field { | ||
name: Option<String>, | ||
value: Option<String>, | ||
} | ||
|
||
pub enum CipherType { | ||
Login(CipherLogin), | ||
Identity(), | ||
} | ||
|
||
impl ToString for CipherType { | ||
fn to_string(&self) -> String { | ||
match self { | ||
CipherType::Login(_) => "login".to_string(), | ||
CipherType::Identity() => "identity".to_string(), | ||
} | ||
} | ||
} | ||
|
||
pub struct CipherLogin { | ||
pub username: String, | ||
pub password: String, | ||
pub login_uris: Vec<String>, | ||
pub totp: Option<String>, | ||
} | ||
|
||
pub fn export(folders: Vec<Folder>, ciphers: Vec<Cipher>, format: Format) -> String { | ||
match format { | ||
Format::Csv => export_csv(folders, ciphers).unwrap(), | ||
Format::Json => export_json(folders, ciphers).unwrap(), | ||
// Format::EncryptedJson { password } => export_encrypted_json(folders, ciphers, password), | ||
_ => todo!(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.