bitwarden_importers/importers/onepassword/convert/
mod.rs1mod card;
13mod claimed;
14mod credential;
15mod field;
16mod identity;
17mod login;
18mod ssh_key;
19#[cfg(test)]
20mod tests;
21mod value;
22
23use bitwarden_exporters::{CipherType, Field, ImportingCipher, SecureNote, SecureNoteType};
24use chrono::Utc;
25
26use self::{
27 card::card,
28 claimed::Claimed,
29 credential::{
30 API_CREDENTIAL_FIELDS, CredentialFields, DATABASE_FIELDS, EMAIL_ACCOUNT_FIELDS,
31 SERVER_FIELDS, WIRELESS_ROUTER_FIELDS, credential_login, password,
32 },
33 field::{TEXT_FIELD, fields_from_details},
34 identity::identity,
35 login::login,
36 ssh_key::ssh_key,
37 value::non_blank,
38};
39use crate::{
40 importers::onepassword::access::{
41 model::{Item, ItemCategory, Vault},
42 wire::VaultItemOverview,
43 },
44 pipeline::ParsedImport,
45};
46
47const UNTITLED_ITEM: &str = "--";
49
50const UNNAMED_VAULT: &str = "-";
52
53const TAGS_FIELD: &str = "tags";
55
56const DOCUMENT_NOTE: &str = "This was a 1Password document. The attached file was not imported.";
59
60pub fn convert(vaults: Vec<Vault>) -> ParsedImport {
62 let mut parsed = ParsedImport {
63 ciphers: Vec::new(),
64 folders: Vec::new(),
65 folder_relationships: Vec::new(),
66 };
67
68 for (folder_index, vault) in vaults.into_iter().enumerate() {
69 parsed
70 .folders
71 .push(non_blank(&vault.name).unwrap_or(UNNAMED_VAULT).to_string());
72
73 for item in vault.items {
74 let cipher_index = parsed.ciphers.len();
75 parsed.ciphers.push(convert_item(item));
76 parsed
77 .folder_relationships
78 .push((cipher_index, folder_index));
79 }
80 }
81
82 parsed
83}
84
85fn convert_item(item: Item) -> ImportingCipher {
86 let now = Utc::now();
88 let (r#type, claimed) = cipher_type(&item);
89 let mut fields = fields_from_details(&item.details, &claimed);
90 fields.extend(tags_field(&item.overview));
91
92 ImportingCipher {
93 folder_id: None,
94 name: item
95 .overview
96 .title
97 .as_deref()
98 .and_then(non_blank)
99 .unwrap_or(UNTITLED_ITEM)
100 .to_string(),
101 notes: notes(&item),
102 r#type,
103 favorite: false,
104 reprompt: 0,
105 fields,
106 revision_date: now,
107 creation_date: now,
108 deleted_date: None,
109 }
110}
111
112fn notes(item: &Item) -> Option<String> {
115 let note = item.details.note.as_deref().and_then(non_blank);
116
117 match (item.category == ItemCategory::Document, note) {
118 (true, Some(note)) => Some(format!("{note}\n\n{DOCUMENT_NOTE}")),
119 (true, None) => Some(DOCUMENT_NOTE.to_string()),
120 (false, note) => note.map(str::to_string),
121 }
122}
123
124fn tags_field(overview: &VaultItemOverview) -> Option<Field> {
127 let tags: Vec<&str> = overview
128 .tags
129 .iter()
130 .flatten()
131 .filter_map(|tag| non_blank(tag))
132 .collect();
133
134 (!tags.is_empty()).then(|| Field {
135 name: Some(TAGS_FIELD.to_string()),
136 value: Some(tags.join(", ")),
137 r#type: TEXT_FIELD,
138 linked_id: None,
139 })
140}
141
142fn cipher_type(item: &Item) -> (CipherType, Claimed) {
146 let (overview, details) = (&item.overview, &item.details);
147 let credential = |fields: &CredentialFields| {
148 typed(
149 credential_login(overview, details, fields),
150 CipherType::Login,
151 )
152 };
153
154 match item.category {
155 ItemCategory::Login => typed(login(overview, details), CipherType::Login),
156 ItemCategory::Password => typed(password(overview, details), CipherType::Login),
157 ItemCategory::Server => credential(&SERVER_FIELDS),
158 ItemCategory::Database => credential(&DATABASE_FIELDS),
159 ItemCategory::ApiCredential => credential(&API_CREDENTIAL_FIELDS),
160 ItemCategory::EmailAccount => credential(&EMAIL_ACCOUNT_FIELDS),
161 ItemCategory::WirelessRouter => credential(&WIRELESS_ROUTER_FIELDS),
162 ItemCategory::CreditCard => typed(card(details), CipherType::Card),
163 ItemCategory::Identity => typed(identity(details), CipherType::Identity),
164 ItemCategory::SshKey => {
166 ssh_key(details).map_or_else(secure_note, |key| typed(key, CipherType::SshKey))
167 }
168 _ => secure_note(),
169 }
170}
171
172fn typed<T>(
174 (mapped, claimed): (T, Claimed),
175 cipher_type: fn(Box<T>) -> CipherType,
176) -> (CipherType, Claimed) {
177 (cipher_type(Box::new(mapped)), claimed)
178}
179
180fn secure_note() -> (CipherType, Claimed) {
181 (
182 CipherType::SecureNote(Box::new(SecureNote {
183 r#type: SecureNoteType::Generic,
184 })),
185 Claimed::default(),
186 )
187}