1pub mod bitwarden;
4pub mod chrome;
5pub mod dashlane;
6pub mod firefox;
7pub mod macos;
8pub mod one_password;
9
10use crate::Convert;
11use async_trait::async_trait;
12use sos_backend::AccessPoint;
13use sos_core::{crypto::AccessKey, UtcDateTime};
14use sos_search::SearchIndex;
15use sos_vault::{
16 secret::{
17 IdentityKind, Secret, SecretId, SecretMeta, SecretRow, UserData,
18 },
19 SecretAccess, Vault,
20};
21use std::collections::{HashMap, HashSet};
22use url::Url;
23use vcard4::Vcard;
24
25pub const UNTITLED: &str = "Untitled";
27
28pub enum GenericCsvEntry {
30 Password(GenericPasswordRecord),
32 Note(GenericNoteRecord),
34 Id(GenericIdRecord),
36 Payment(GenericPaymentRecord),
38 Contact(Box<GenericContactRecord>),
40}
41
42impl GenericCsvEntry {
43 fn label(&self) -> &str {
45 match self {
46 Self::Password(record) => &record.label,
47 Self::Note(record) => &record.label,
48 Self::Id(record) => &record.label,
49 Self::Payment(record) => record.label(),
50 Self::Contact(record) => &record.label,
51 }
52 }
53
54 fn tags(&mut self) -> &mut Option<HashSet<String>> {
56 match self {
57 Self::Password(record) => &mut record.tags,
58 Self::Note(record) => &mut record.tags,
59 Self::Id(record) => &mut record.tags,
60 Self::Payment(record) => record.tags(),
61 Self::Contact(record) => &mut record.tags,
62 }
63 }
64
65 fn note(&mut self) -> &mut Option<String> {
67 match self {
68 Self::Password(record) => &mut record.note,
69 Self::Note(record) => &mut record.note,
70 Self::Id(record) => &mut record.note,
71 Self::Payment(record) => record.note(),
72 Self::Contact(record) => &mut record.note,
73 }
74 }
75}
76
77impl From<GenericCsvEntry> for Secret {
78 fn from(value: GenericCsvEntry) -> Self {
79 match value {
80 GenericCsvEntry::Password(record) => Secret::Account {
81 account: record.username,
82 password: record.password.into(),
83 url: record.url,
84 user_data: if let Some(notes) = record.note {
85 UserData::new_comment(notes)
86 } else {
87 Default::default()
88 },
89 },
90 GenericCsvEntry::Note(record) => Secret::Note {
91 text: record.text.into(),
92 user_data: if let Some(notes) = record.note {
93 UserData::new_comment(notes)
94 } else {
95 Default::default()
96 },
97 },
98 GenericCsvEntry::Id(record) => Secret::Identity {
99 id_kind: record.id_kind,
100 number: record.number.into(),
101 issue_place: record.issue_place,
102 issue_date: record.issue_date,
103 expiry_date: record.expiration_date,
104 user_data: if let Some(notes) = record.note {
105 UserData::new_comment(notes)
106 } else {
107 Default::default()
108 },
109 },
110 GenericCsvEntry::Payment(record) => match record {
111 GenericPaymentRecord::Card {
112 number,
113 code,
114 expiration,
115 note,
116 ..
117 } => {
118 Secret::Card {
120 number: number.into(),
121 cvv: code.into(),
122 expiry: expiration,
123 name: None,
124 atm_pin: None,
125 user_data: if let Some(notes) = note {
126 UserData::new_comment(notes)
127 } else {
128 Default::default()
129 },
130 }
131 }
132 GenericPaymentRecord::BankAccount {
133 account_number,
134 routing_number,
135 note,
136 ..
137 } => {
138 Secret::Bank {
140 number: account_number.into(),
141 routing: routing_number.into(),
142 bic: None,
143 iban: None,
144 swift: None,
145 user_data: if let Some(notes) = note {
146 UserData::new_comment(notes)
147 } else {
148 Default::default()
149 },
150 }
151 }
152 },
153 GenericCsvEntry::Contact(record) => Secret::Contact {
154 vcard: Box::new(record.vcard),
155 user_data: if let Some(notes) = record.note {
156 UserData::new_comment(notes)
157 } else {
158 Default::default()
159 },
160 },
161 }
162 }
163}
164
165pub struct GenericPasswordRecord {
167 pub label: String,
169 pub url: Vec<Url>,
171 pub username: String,
173 pub password: String,
175 pub otp_auth: Option<String>,
177 pub tags: Option<HashSet<String>>,
179 pub note: Option<String>,
181}
182
183pub struct GenericNoteRecord {
185 pub label: String,
187 pub text: String,
189 pub tags: Option<HashSet<String>>,
191 pub note: Option<String>,
193}
194
195pub struct GenericContactRecord {
197 pub label: String,
199 pub vcard: Vcard,
201 pub tags: Option<HashSet<String>>,
203 pub note: Option<String>,
205}
206
207pub struct GenericIdRecord {
209 pub label: String,
211 pub id_kind: IdentityKind,
213 pub number: String,
215 pub issue_place: Option<String>,
217 pub issue_date: Option<UtcDateTime>,
219 pub expiration_date: Option<UtcDateTime>,
221 pub tags: Option<HashSet<String>>,
223 pub note: Option<String>,
225}
226
227pub enum GenericPaymentRecord {
229 Card {
231 label: String,
233 number: String,
235 code: String,
237 expiration: Option<UtcDateTime>,
239 country: String,
241 note: Option<String>,
243 tags: Option<HashSet<String>>,
245 },
246 BankAccount {
248 label: String,
250 account_holder: String,
252 account_number: String,
254 routing_number: String,
256 country: String,
258 note: Option<String>,
260 tags: Option<HashSet<String>>,
262 },
263}
264
265impl GenericPaymentRecord {
266 fn label(&self) -> &str {
268 match self {
269 Self::Card { label, .. } => label,
270 Self::BankAccount { label, .. } => label,
271 }
272 }
273
274 fn tags(&mut self) -> &mut Option<HashSet<String>> {
276 match self {
277 Self::Card { tags, .. } => tags,
278 Self::BankAccount { tags, .. } => tags,
279 }
280 }
281
282 fn note(&mut self) -> &mut Option<String> {
284 match self {
285 Self::Card { note, .. } => note,
286 Self::BankAccount { note, .. } => note,
287 }
288 }
289}
290
291pub struct GenericCsvConvert;
293
294#[async_trait]
295impl Convert for GenericCsvConvert {
296 type Input = Vec<GenericCsvEntry>;
297
298 async fn convert(
299 &self,
300 source: Self::Input,
301 vault: Vault,
302 key: &AccessKey,
303 ) -> crate::Result<Vault> {
304 let mut index = SearchIndex::new();
305 let mut keeper = AccessPoint::from_vault(vault);
306 keeper.unlock(key).await?;
307
308 let mut duplicates: HashMap<String, usize> = HashMap::new();
309
310 for mut entry in source {
311 let mut label = entry.label().to_owned();
313
314 let rename_label = {
315 if index
316 .find_by_label(keeper.vault().id(), &label, None)
317 .is_some()
318 {
319 duplicates
320 .entry(label.clone())
321 .and_modify(|counter| *counter += 1)
322 .or_insert(1);
323 let counter = duplicates.get(&label).unwrap();
324 Some(format!("{} {}", label, counter))
325 } else {
326 None
327 }
328 };
329
330 if let Some(renamed) = rename_label {
331 label = renamed;
332 }
333
334 let tags = entry.tags().take();
335 let note = entry.note().take();
336 let mut secret: Secret = entry.into();
337 secret.user_data_mut().set_comment(note);
338 let mut meta = SecretMeta::new(label, secret.kind());
339 if let Some(tags) = tags {
340 meta.set_tags(tags);
341 }
342
343 let id = SecretId::new_v4();
344 let index_doc = index.prepare(keeper.id(), &id, &meta, &secret);
345 let secret_data = SecretRow::new(id, meta, secret);
346 keeper.create_secret(&secret_data).await?;
347 index.commit(index_doc);
348 }
349
350 keeper.lock();
351 Ok(keeper.into())
352 }
353}