Skip to content

Commit 29c6158

Browse files
abergsHinton
andauthored
PM-23655: Map WI-FI -> Note (#358)
Maps wifi into a note on import Co-authored-by: Hinton <[email protected]>
1 parent 5ca155b commit 29c6158

File tree

4 files changed

+469
-2
lines changed

4 files changed

+469
-2
lines changed
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
use bitwarden_vault::FieldType;
2+
use credential_exchange_format::{
3+
EditableField, EditableFieldBoolean, EditableFieldConcealedString, EditableFieldString,
4+
EditableFieldWifiNetworkSecurityType,
5+
};
6+
7+
use crate::Field;
8+
9+
/// Helper function to create a Field from any EditableField type
10+
pub(super) fn create_field<T>(name: impl Into<String>, field: &T) -> Field
11+
where
12+
T: EditableFieldToField,
13+
{
14+
Field {
15+
name: Some(name.into()),
16+
value: Some(field.field_value()),
17+
r#type: T::FIELD_TYPE as u8,
18+
linked_id: None,
19+
}
20+
}
21+
22+
/// Trait to convert CXP EditableField types to Bitwarden Field values and types
23+
pub(super) trait EditableFieldToField {
24+
const FIELD_TYPE: FieldType;
25+
26+
fn field_value(&self) -> String;
27+
}
28+
29+
impl EditableFieldToField for EditableField<EditableFieldString> {
30+
const FIELD_TYPE: FieldType = FieldType::Text;
31+
32+
fn field_value(&self) -> String {
33+
self.value.0.clone()
34+
}
35+
}
36+
37+
impl EditableFieldToField for EditableField<EditableFieldConcealedString> {
38+
const FIELD_TYPE: FieldType = FieldType::Hidden;
39+
40+
fn field_value(&self) -> String {
41+
self.value.0.clone()
42+
}
43+
}
44+
45+
impl EditableFieldToField for EditableField<EditableFieldBoolean> {
46+
const FIELD_TYPE: FieldType = FieldType::Boolean;
47+
48+
fn field_value(&self) -> String {
49+
self.value.0.to_string()
50+
}
51+
}
52+
53+
impl EditableFieldToField for EditableField<EditableFieldWifiNetworkSecurityType> {
54+
const FIELD_TYPE: FieldType = FieldType::Text;
55+
56+
fn field_value(&self) -> String {
57+
security_type_to_string(&self.value).to_string()
58+
}
59+
}
60+
61+
/// Convert WiFi security type enum to human-readable string
62+
fn security_type_to_string(security_type: &EditableFieldWifiNetworkSecurityType) -> &str {
63+
use EditableFieldWifiNetworkSecurityType::*;
64+
match security_type {
65+
Unsecured => "Unsecured",
66+
WpaPersonal => "WPA Personal",
67+
Wpa2Personal => "WPA2 Personal",
68+
Wpa3Personal => "WPA3 Personal",
69+
Wep => "WEP",
70+
Other(s) => s,
71+
}
72+
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use super::*;
77+
78+
#[test]
79+
fn test_create_field_string() {
80+
let editable_field = EditableField {
81+
id: None,
82+
label: None,
83+
value: EditableFieldString("Test Value".to_string()),
84+
extensions: None,
85+
};
86+
87+
let field = create_field("Test Name", &editable_field);
88+
89+
assert_eq!(
90+
field,
91+
Field {
92+
name: Some("Test Name".to_string()),
93+
value: Some("Test Value".to_string()),
94+
r#type: FieldType::Text as u8,
95+
linked_id: None,
96+
}
97+
);
98+
}
99+
100+
#[test]
101+
fn test_create_field_concealed_string() {
102+
let editable_field = EditableField {
103+
id: None,
104+
label: None,
105+
value: EditableFieldConcealedString("Secret123".to_string()),
106+
extensions: None,
107+
};
108+
109+
let field = create_field("Password", &editable_field);
110+
111+
assert_eq!(
112+
field,
113+
Field {
114+
name: Some("Password".to_string()),
115+
value: Some("Secret123".to_string()),
116+
r#type: FieldType::Hidden as u8,
117+
linked_id: None,
118+
}
119+
);
120+
}
121+
122+
#[test]
123+
fn test_create_field_boolean_true() {
124+
let editable_field = EditableField {
125+
id: None,
126+
label: None,
127+
value: EditableFieldBoolean(true),
128+
extensions: None,
129+
};
130+
131+
let field = create_field("Is Enabled", &editable_field);
132+
133+
assert_eq!(
134+
field,
135+
Field {
136+
name: Some("Is Enabled".to_string()),
137+
value: Some("true".to_string()),
138+
r#type: FieldType::Boolean as u8,
139+
linked_id: None,
140+
}
141+
);
142+
}
143+
144+
#[test]
145+
fn test_create_field_boolean_false() {
146+
let editable_field = EditableField {
147+
id: None,
148+
label: None,
149+
value: EditableFieldBoolean(false),
150+
extensions: None,
151+
};
152+
153+
let field = create_field("Is Hidden", &editable_field);
154+
155+
assert_eq!(
156+
field,
157+
Field {
158+
name: Some("Is Hidden".to_string()),
159+
value: Some("false".to_string()),
160+
r#type: FieldType::Boolean as u8,
161+
linked_id: None,
162+
}
163+
);
164+
}
165+
166+
#[test]
167+
fn test_create_field_wifi_security() {
168+
let editable_field = EditableField {
169+
id: None,
170+
label: None,
171+
value: EditableFieldWifiNetworkSecurityType::Wpa3Personal,
172+
extensions: None,
173+
};
174+
175+
let field = create_field("WiFi Security", &editable_field);
176+
177+
assert_eq!(
178+
field,
179+
Field {
180+
name: Some("WiFi Security".to_string()),
181+
value: Some("WPA3 Personal".to_string()),
182+
r#type: FieldType::Text as u8,
183+
linked_id: None,
184+
}
185+
);
186+
}
187+
188+
#[test]
189+
fn test_security_type_to_string() {
190+
assert_eq!(
191+
security_type_to_string(&EditableFieldWifiNetworkSecurityType::Unsecured),
192+
"Unsecured"
193+
);
194+
assert_eq!(
195+
security_type_to_string(&EditableFieldWifiNetworkSecurityType::WpaPersonal),
196+
"WPA Personal"
197+
);
198+
assert_eq!(
199+
security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wpa2Personal),
200+
"WPA2 Personal"
201+
);
202+
assert_eq!(
203+
security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wpa3Personal),
204+
"WPA3 Personal"
205+
);
206+
assert_eq!(
207+
security_type_to_string(&EditableFieldWifiNetworkSecurityType::Wep),
208+
"WEP"
209+
);
210+
211+
let custom_security = "WPA2 Enterprise";
212+
assert_eq!(
213+
security_type_to_string(&EditableFieldWifiNetworkSecurityType::Other(
214+
custom_security.to_string()
215+
)),
216+
custom_security
217+
);
218+
}
219+
}

crates/bitwarden-exporters/src/cxf/import.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use chrono::{DateTime, Utc};
22
use credential_exchange_format::{
33
Account as CxfAccount, BasicAuthCredential, Credential, CreditCardCredential, Item,
4-
PasskeyCredential,
4+
PasskeyCredential, WifiCredential,
55
};
66

77
use crate::{
88
cxf::{
99
login::{to_fields, to_login},
10+
wifi::wifi_to_fields,
1011
CxfError,
1112
},
12-
CipherType, ImportingCipher,
13+
CipherType, ImportingCipher, SecureNote, SecureNoteType,
1314
};
1415

1516
pub(crate) fn parse_cxf(payload: String) -> Result<Vec<ImportingCipher>, CxfError> {
@@ -79,6 +80,26 @@ fn parse_item(value: Item) -> Vec<ImportingCipher> {
7980
})
8081
}
8182

83+
// WiFi credentials -> Secure Note
84+
if let Some(wifi) = grouped.wifi.first() {
85+
let fields = wifi_to_fields(wifi);
86+
87+
output.push(ImportingCipher {
88+
folder_id: None, // TODO: Handle folders
89+
name: value.title.clone(),
90+
notes: None,
91+
r#type: CipherType::SecureNote(Box::new(SecureNote {
92+
r#type: SecureNoteType::Generic,
93+
})),
94+
favorite: false,
95+
reprompt: 0,
96+
fields,
97+
revision_date,
98+
creation_date,
99+
deleted_date: None,
100+
})
101+
}
102+
82103
output
83104
}
84105

@@ -112,13 +133,18 @@ fn group_credentials_by_type(credentials: Vec<Credential>) -> GroupedCredentials
112133
Credential::CreditCard(credit_card) => Some(credit_card.as_ref()),
113134
_ => None,
114135
}),
136+
wifi: filter_credentials(&credentials, |c| match c {
137+
Credential::Wifi(wifi) => Some(wifi.as_ref()),
138+
_ => None,
139+
}),
115140
}
116141
}
117142

118143
struct GroupedCredentials {
119144
basic_auth: Vec<BasicAuthCredential>,
120145
passkey: Vec<PasskeyCredential>,
121146
credit_card: Vec<CreditCardCredential>,
147+
wifi: Vec<WifiCredential>,
122148
}
123149

124150
#[cfg(test)]

crates/bitwarden-exporters/src/cxf/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ pub use export::Account;
1313
mod import;
1414
pub(crate) use import::parse_cxf;
1515
mod card;
16+
mod editable_field;
1617
mod login;
18+
mod wifi;

0 commit comments

Comments
 (0)