bitwarden_exporters/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::fmt;
4
5use bitwarden_vault::{
6    CipherRepromptType, CipherView, Fido2CredentialFullView, LoginUriView, UriMatchType,
7};
8use chrono::{DateTime, Utc};
9use uuid::Uuid;
10
11#[cfg(feature = "uniffi")]
12uniffi::setup_scaffolding!();
13#[cfg(feature = "uniffi")]
14mod uniffi_support;
15
16mod csv;
17mod cxf;
18pub use cxf::Account;
19mod encrypted_json;
20mod exporter_client;
21mod json;
22mod models;
23pub use exporter_client::{ExporterClient, ExporterClientExt};
24mod error;
25mod export;
26pub use error::ExportError;
27
28#[allow(missing_docs)]
29#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
30#[cfg_attr(
31    feature = "wasm",
32    derive(serde::Serialize, serde::Deserialize, tsify_next::Tsify),
33    tsify(into_wasm_abi, from_wasm_abi)
34)]
35pub enum ExportFormat {
36    Csv,
37    Json,
38    EncryptedJson { password: String },
39}
40
41/// Export representation of a Bitwarden folder.
42///
43/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
44/// that is not tied to the internal vault models. We may revisit this in the future.
45#[allow(missing_docs)]
46pub struct Folder {
47    pub id: Uuid,
48    pub name: String,
49}
50
51/// Export representation of a Bitwarden cipher.
52///
53/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
54/// that is not tied to the internal vault models. We may revisit this in the future.
55#[allow(missing_docs)]
56#[derive(Clone)]
57pub struct Cipher {
58    pub id: Uuid,
59    pub folder_id: Option<Uuid>,
60
61    pub name: String,
62    pub notes: Option<String>,
63
64    pub r#type: CipherType,
65
66    pub favorite: bool,
67    pub reprompt: u8,
68
69    pub fields: Vec<Field>,
70
71    pub revision_date: DateTime<Utc>,
72    pub creation_date: DateTime<Utc>,
73    pub deleted_date: Option<DateTime<Utc>>,
74}
75
76/// Import representation of a Bitwarden cipher.
77///
78/// These are mostly duplicated from the `bitwarden` vault models to facilitate a stable export API
79/// that is not tied to the internal vault models. We may revisit this in the future.
80#[allow(missing_docs)]
81#[derive(Clone)]
82pub struct ImportingCipher {
83    pub folder_id: Option<Uuid>,
84
85    pub name: String,
86    pub notes: Option<String>,
87
88    pub r#type: CipherType,
89
90    pub favorite: bool,
91    pub reprompt: u8,
92
93    pub fields: Vec<Field>,
94
95    pub revision_date: DateTime<Utc>,
96    pub creation_date: DateTime<Utc>,
97    pub deleted_date: Option<DateTime<Utc>>,
98}
99
100impl From<ImportingCipher> for CipherView {
101    fn from(value: ImportingCipher) -> Self {
102        let login = match value.r#type {
103            CipherType::Login(login) => {
104                let l: Vec<LoginUriView> = login
105                    .login_uris
106                    .into_iter()
107                    .map(LoginUriView::from)
108                    .collect();
109
110                Some(bitwarden_vault::LoginView {
111                    username: login.username,
112                    password: login.password,
113                    password_revision_date: None,
114                    uris: if l.is_empty() { None } else { Some(l) },
115                    totp: login.totp,
116                    autofill_on_page_load: None,
117                    fido2_credentials: None,
118                })
119            }
120            _ => None,
121        };
122
123        Self {
124            id: None,
125            organization_id: None,
126            folder_id: value.folder_id,
127            collection_ids: vec![],
128            key: None,
129            name: value.name,
130            notes: None,
131            r#type: bitwarden_vault::CipherType::Login,
132            login,
133            identity: None,
134            card: None,
135            secure_note: None,
136            ssh_key: None,
137            favorite: value.favorite,
138            reprompt: CipherRepromptType::None,
139            organization_use_totp: true,
140            edit: true,
141            permissions: None,
142            view_password: true,
143            local_data: None,
144            attachments: None,
145            fields: None,
146            password_history: None,
147            creation_date: value.creation_date,
148            deleted_date: None,
149            revision_date: value.revision_date,
150        }
151    }
152}
153
154impl From<LoginUri> for bitwarden_vault::LoginUriView {
155    fn from(value: LoginUri) -> Self {
156        Self {
157            uri: value.uri,
158            r#match: value.r#match.and_then(|m| match m {
159                0 => Some(UriMatchType::Domain),
160                1 => Some(UriMatchType::Host),
161                2 => Some(UriMatchType::StartsWith),
162                3 => Some(UriMatchType::Exact),
163                4 => Some(UriMatchType::RegularExpression),
164                5 => Some(UriMatchType::Never),
165                _ => None,
166            }),
167            uri_checksum: None,
168        }
169    }
170}
171
172#[allow(missing_docs)]
173#[derive(Clone)]
174pub struct Field {
175    pub name: Option<String>,
176    pub value: Option<String>,
177    pub r#type: u8,
178    pub linked_id: Option<u32>,
179}
180
181#[allow(missing_docs)]
182#[derive(Clone)]
183pub enum CipherType {
184    Login(Box<Login>),
185    SecureNote(Box<SecureNote>),
186    Card(Box<Card>),
187    Identity(Box<Identity>),
188    SshKey(Box<SshKey>),
189}
190
191impl fmt::Display for CipherType {
192    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193        match self {
194            CipherType::Login(_) => write!(f, "login"),
195            CipherType::SecureNote(_) => write!(f, "note"),
196            CipherType::Card(_) => write!(f, "card"),
197            CipherType::Identity(_) => write!(f, "identity"),
198            CipherType::SshKey(_) => write!(f, "ssh_key"),
199        }
200    }
201}
202
203#[allow(missing_docs)]
204#[derive(Clone)]
205pub struct Login {
206    pub username: Option<String>,
207    pub password: Option<String>,
208    pub login_uris: Vec<LoginUri>,
209    pub totp: Option<String>,
210
211    pub fido2_credentials: Option<Vec<Fido2Credential>>,
212}
213
214#[allow(missing_docs)]
215#[derive(Clone)]
216pub struct LoginUri {
217    pub uri: Option<String>,
218    pub r#match: Option<u8>,
219}
220
221#[allow(missing_docs)]
222#[derive(Clone)]
223pub struct Fido2Credential {
224    pub credential_id: String,
225    pub key_type: String,
226    pub key_algorithm: String,
227    pub key_curve: String,
228    pub key_value: String,
229    pub rp_id: String,
230    pub user_handle: Option<String>,
231    pub user_name: Option<String>,
232    pub counter: u32,
233    pub rp_name: Option<String>,
234    pub user_display_name: Option<String>,
235    pub discoverable: String,
236    pub creation_date: DateTime<Utc>,
237}
238
239impl From<Fido2Credential> for Fido2CredentialFullView {
240    fn from(value: Fido2Credential) -> Self {
241        Fido2CredentialFullView {
242            credential_id: value.credential_id,
243            key_type: value.key_type,
244            key_algorithm: value.key_algorithm,
245            key_curve: value.key_curve,
246            key_value: value.key_value,
247            rp_id: value.rp_id,
248            user_handle: value.user_handle,
249            user_name: value.user_name,
250            counter: value.counter.to_string(),
251            rp_name: value.rp_name,
252            user_display_name: value.user_display_name,
253            discoverable: value.discoverable,
254            creation_date: value.creation_date,
255        }
256    }
257}
258
259#[allow(missing_docs)]
260#[derive(Clone)]
261pub struct Card {
262    pub cardholder_name: Option<String>,
263    pub exp_month: Option<String>,
264    pub exp_year: Option<String>,
265    pub code: Option<String>,
266    pub brand: Option<String>,
267    pub number: Option<String>,
268}
269
270#[allow(missing_docs)]
271#[derive(Clone)]
272pub struct SecureNote {
273    pub r#type: SecureNoteType,
274}
275
276#[allow(missing_docs)]
277#[derive(Clone)]
278pub enum SecureNoteType {
279    Generic = 0,
280}
281
282#[allow(missing_docs)]
283#[derive(Clone)]
284pub struct Identity {
285    pub title: Option<String>,
286    pub first_name: Option<String>,
287    pub middle_name: Option<String>,
288    pub last_name: Option<String>,
289    pub address1: Option<String>,
290    pub address2: Option<String>,
291    pub address3: Option<String>,
292    pub city: Option<String>,
293    pub state: Option<String>,
294    pub postal_code: Option<String>,
295    pub country: Option<String>,
296    pub company: Option<String>,
297    pub email: Option<String>,
298    pub phone: Option<String>,
299    pub ssn: Option<String>,
300    pub username: Option<String>,
301    pub passport_number: Option<String>,
302    pub license_number: Option<String>,
303}
304
305#[allow(missing_docs)]
306#[derive(Clone)]
307pub struct SshKey {
308    /// [OpenSSH private key](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key), in PEM encoding.
309    pub private_key: String,
310    /// Ssh public key (ed25519/rsa) according to [RFC4253](https://datatracker.ietf.org/doc/html/rfc4253#section-6.6)
311    pub public_key: String,
312    /// SSH fingerprint using SHA256 in the format: `SHA256:BASE64_ENCODED_FINGERPRINT`
313    pub fingerprint: String,
314}