bitwarden_importers/importers/onepassword/convert/
field.rs1use bitwarden_exporters::Field;
5
6use super::{
7 claimed::{Claimed, section_fields},
8 value::{non_blank, render_value},
9};
10use crate::importers::onepassword::access::wire::{SshKeyAttributes, VaultItemDetails};
11
12pub(super) const TEXT_FIELD: u8 = 0;
14pub(super) const HIDDEN_FIELD: u8 = 1;
15
16pub(super) fn fields_from_details(details: &VaultItemDetails, claimed: &Claimed) -> Vec<Field> {
21 let mut fields = Vec::new();
22 let mut designations = claimed.designations.to_vec();
25
26 for field in details.fields.iter().flatten() {
28 let designation = field.designation.as_deref().unwrap_or_default();
29 if let Some(claim) = designations.iter().position(|taken| *taken == designation) {
30 designations.remove(claim);
31 continue;
32 }
33
34 push_field(
35 &mut fields,
36 field
37 .name
38 .as_deref()
39 .and_then(non_blank)
40 .or(non_blank(designation)),
41 field
42 .value
43 .as_deref()
44 .and_then(non_blank)
45 .map(str::to_string),
46 field.kind.as_deref() == Some("P"),
47 );
48 }
49
50 for (position, field) in section_fields(details).enumerate() {
52 if claimed.fields.contains(&position) {
53 continue;
54 }
55
56 let name = field
58 .name
59 .as_deref()
60 .and_then(non_blank)
61 .or(field.id.as_deref().and_then(non_blank));
62 let hidden = matches!(field.kind.as_deref(), Some("concealed" | "sshKey"));
63 if let Some(attributes) = field
65 .attributes
66 .as_ref()
67 .and_then(|attributes| attributes.ssh_key.as_ref())
68 {
69 push_ssh_attributes(&mut fields, attributes);
70 if let Some(private_key) = attributes.private_key.as_deref()
72 && field.value.as_ref().and_then(serde_json::Value::as_str) == Some(private_key)
73 {
74 continue;
75 }
76 }
77 let Some(value) = field.value.as_ref() else {
78 continue;
79 };
80
81 match (field.kind.as_deref(), value) {
82 (Some("file"), serde_json::Value::Object(_)) => push_field(
86 &mut fields,
87 name,
88 Some(format!("<attachment: {}>", attachment_name(value, name))),
89 false,
90 ),
91 (Some("address"), serde_json::Value::Object(parts)) => {
94 for (part, value) in parts {
95 push_field(&mut fields, Some(part), render_value(None, value), hidden);
96 }
97 }
98 _ => push_field(
100 &mut fields,
101 name,
102 render_value(field.kind.as_deref(), value),
103 hidden,
104 ),
105 }
106 }
107
108 fields
109}
110
111fn attachment_name<'a>(value: &'a serde_json::Value, fallback: Option<&'a str>) -> &'a str {
112 value
113 .get("fileName")
114 .and_then(serde_json::Value::as_str)
115 .and_then(non_blank)
116 .or(fallback)
117 .unwrap_or("unnamed")
118}
119
120fn push_ssh_attributes(fields: &mut Vec<Field>, attributes: &SshKeyAttributes) {
123 for (name, value, hidden) in [
124 ("private key", &attributes.private_key, true),
125 ("public key", &attributes.public_key, false),
126 ("fingerprint", &attributes.fingerprint, false),
127 ] {
128 push_field(
129 fields,
130 Some(name),
131 value.as_deref().and_then(non_blank).map(str::to_string),
132 hidden,
133 );
134 }
135}
136
137fn push_field(fields: &mut Vec<Field>, name: Option<&str>, value: Option<String>, hidden: bool) {
140 let Some(value) = value else {
141 return;
142 };
143
144 fields.push(Field {
145 name: name.map(str::to_string),
146 value: Some(value),
147 r#type: if hidden { HIDDEN_FIELD } else { TEXT_FIELD },
148 linked_id: None,
149 });
150}