rucksack_db/csv/
firefox.rs

1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4use rucksack_lib::time;
5
6use crate::records;
7use crate::records::secrets_from_user_pass;
8
9// This started as the Firefox login data struct, but it has more fields than
10// others, so it has become the default interim struct to which others convert
11// for imports.
12#[derive(Debug, Default, Deserialize, Serialize)]
13#[serde(rename_all = "camelCase")]
14pub struct Record {
15    pub url: String,
16    pub username: String,
17    pub password: String,
18    pub http_realm: String,
19    pub form_action_origin: String,
20    pub guid: String,
21    pub time_created: i64,
22    pub time_last_used: i64,
23    pub time_password_changed: i64,
24}
25
26pub fn new(url: String, username: String) -> Record {
27    new_with_password(url, username, "".to_string())
28}
29
30pub fn new_with_password(url: String, username: String, password: String) -> Record {
31    Record {
32        url,
33        username,
34        password,
35
36        ..Default::default()
37    }
38}
39
40impl Record {
41    pub fn to_decrypted(&self) -> records::DecryptedRecord {
42        let secrets = secrets_from_user_pass(self.username.as_str(), self.password.as_str());
43        let mut metadata = records::default_metadata();
44        metadata.name = secrets.user.clone();
45        metadata.url = self.url.clone();
46        metadata.created = time::epoch_to_string(self.time_created);
47        metadata.password_changed = time::epoch_to_string(self.time_password_changed);
48        metadata.last_used = time::epoch_to_string(self.time_last_used);
49        records::DecryptedRecord {
50            secrets,
51            metadata,
52            history: Vec::<records::History>::new(),
53        }
54    }
55}
56
57pub fn from_decrypted(dr: records::DecryptedRecord) -> Record {
58    let md = dr.metadata();
59    let mut name = md.name.clone();
60    if name.is_empty() {
61        name = dr.secrets.user.clone();
62    };
63    Record {
64        url: md.url.clone(),
65        username: name,
66        password: dr.password(),
67        form_action_origin: md.url,
68        guid: Uuid::new_v4().to_string(),
69        time_created: time::string_to_epoch(md.created),
70        time_last_used: time::string_to_epoch(md.last_used),
71        time_password_changed: time::string_to_epoch(md.password_changed),
72
73        ..Default::default()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_new() {
83        let record = new("https://example.com".to_string(), "testuser".to_string());
84        assert_eq!(record.url, "https://example.com");
85        assert_eq!(record.username, "testuser");
86        assert_eq!(record.password, "");
87    }
88
89    #[test]
90    fn test_new_with_password() {
91        let record = new_with_password(
92            "https://example.com".to_string(),
93            "testuser".to_string(),
94            "testpass".to_string(),
95        );
96        assert_eq!(record.url, "https://example.com");
97        assert_eq!(record.username, "testuser");
98        assert_eq!(record.password, "testpass");
99    }
100
101    #[test]
102    fn test_to_decrypted() {
103        let record = new_with_password(
104            "https://example.com".to_string(),
105            "testuser".to_string(),
106            "testpass".to_string(),
107        );
108        let decrypted = record.to_decrypted();
109        assert_eq!(decrypted.secrets.user, "testuser");
110        assert_eq!(decrypted.secrets.password, "testpass");
111        assert_eq!(decrypted.metadata.url, "https://example.com");
112        assert_eq!(decrypted.metadata.name, "testuser");
113    }
114
115    #[test]
116    fn test_to_decrypted_with_timestamps() {
117        let record = Record {
118            url: "https://example.com".to_string(),
119            username: "testuser".to_string(),
120            password: "testpass".to_string(),
121            time_created: 1609459200,
122            time_last_used: 1609545600,
123            time_password_changed: 1609632000,
124            ..Default::default()
125        };
126        let decrypted = record.to_decrypted();
127        assert!(!decrypted.metadata.created.is_empty());
128        assert!(!decrypted.metadata.last_used.is_empty());
129        assert!(!decrypted.metadata.password_changed.is_empty());
130    }
131
132    #[test]
133    fn test_from_decrypted() {
134        let mut decrypted = records::DecryptedRecord {
135            secrets: secrets_from_user_pass("testuser", "testpass"),
136            metadata: records::default_metadata(),
137            history: Vec::new(),
138        };
139        decrypted.metadata.url = "https://example.com".to_string();
140        decrypted.metadata.name = "testuser".to_string();
141
142        let firefox_record = from_decrypted(decrypted.clone());
143        assert_eq!(firefox_record.url, "https://example.com");
144        assert_eq!(firefox_record.username, "testuser");
145        assert_eq!(firefox_record.password, "testpass");
146        assert!(!firefox_record.guid.is_empty());
147    }
148
149    #[test]
150    fn test_from_decrypted_empty_name() {
151        let mut decrypted = records::DecryptedRecord {
152            secrets: secrets_from_user_pass("testuser", "testpass"),
153            metadata: records::default_metadata(),
154            history: Vec::new(),
155        };
156        decrypted.metadata.url = "https://example.com".to_string();
157        decrypted.metadata.name = "".to_string();
158
159        let firefox_record = from_decrypted(decrypted);
160        assert_eq!(firefox_record.username, "testuser");
161    }
162
163    #[test]
164    fn test_default_record() {
165        let record = Record::default();
166        assert_eq!(record.url, "");
167        assert_eq!(record.username, "");
168        assert_eq!(record.password, "");
169        assert_eq!(record.time_created, 0);
170    }
171}