authenticator/backup/
freeotp.rs1use anyhow::Result;
2use gettextrs::gettext;
3use gtk::prelude::*;
4use serde::{Deserialize, Serialize};
5
6use super::{Backupable, Restorable};
7use crate::models::{Account, OTPUri, Provider, ProvidersModel};
8
9#[allow(clippy::upper_case_acronyms)]
10#[derive(Serialize, Deserialize)]
11pub struct FreeOTP;
12
13impl Backupable for FreeOTP {
14 const ENCRYPTABLE: bool = false;
15 const IDENTIFIER: &'static str = "authenticator";
16
17 fn title() -> String {
18 gettext("_Authenticator")
19 }
20
21 fn subtitle() -> String {
22 gettext("Into a plain-text file, compatible with FreeOTP+")
23 }
24
25 fn backup(model: &ProvidersModel, _key: Option<&str>) -> Result<Vec<u8>> {
26 let mut items: Vec<String> = Vec::new();
27
28 for i in 0..model.n_items() {
29 let provider = model.item(i).and_downcast::<Provider>().unwrap();
30 let accounts = provider.accounts_model();
31
32 for j in 0..accounts.n_items() {
33 let account = accounts.item(j).and_downcast::<Account>().unwrap();
34
35 items.push(account.otp_uri().into());
36 }
37 }
38
39 let content = items.join("\n");
40 Ok(content.as_bytes().to_vec())
41 }
42}
43
44impl Restorable for FreeOTP {
45 const ENCRYPTABLE: bool = false;
46 const SCANNABLE: bool = false;
47 const IDENTIFIER: &'static str = "authenticator";
48 type Item = OTPUri;
49
50 fn title() -> String {
51 gettext("A_uthenticator")
52 }
53
54 fn subtitle() -> String {
55 gettext("From a plain-text file, compatible with FreeOTP+")
56 }
57
58 fn restore_from_data(from: &[u8], _key: Option<&str>) -> Result<Vec<Self::Item>> {
59 let uris = String::from_utf8(from.into())?;
60
61 let items = uris
62 .split('\n')
63 .filter_map(|uri| uri.parse::<OTPUri>().ok())
64 .collect::<Vec<OTPUri>>();
65
66 Ok(items)
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::{super::RestorableItem, *};
73 use crate::models::{Algorithm, Method};
74
75 #[test]
76 fn parse() {
77 let data = std::fs::read_to_string("./src/backup/tests/plain.txt").unwrap();
78 let items = FreeOTP::restore_from_data(data.as_bytes(), None).unwrap();
79
80 assert_eq!(items[0].account(), "Mason");
81 assert_eq!(items[0].issuer(), "Deno");
82 assert_eq!(items[0].secret(), "4SJHB4GSD43FZBAI7C2HLRJGPQ");
83 assert_eq!(items[0].period(), Some(30));
84 assert_eq!(items[0].method(), Method::TOTP);
85 assert_eq!(items[0].algorithm(), Algorithm::SHA1);
86 assert_eq!(items[0].digits(), Some(6));
87 assert_eq!(items[0].counter(), None);
88
89 assert_eq!(items[1].account(), "James");
90 assert_eq!(items[1].issuer(), "SPDX");
91 assert_eq!(items[1].secret(), "5OM4WOOGPLQEF6UGN3CPEOOLWU");
92 assert_eq!(items[1].period(), Some(20));
93 assert_eq!(items[1].method(), Method::TOTP);
94 assert_eq!(items[1].algorithm(), Algorithm::SHA256);
95 assert_eq!(items[1].digits(), Some(7));
96 assert_eq!(items[1].counter(), None);
97
98 assert_eq!(items[2].account(), "Elijah");
99 assert_eq!(items[2].issuer(), "Airbnb");
100 assert_eq!(items[2].secret(), "7ELGJSGXNCCTV3O6LKJWYFV2RA");
101 assert_eq!(items[2].period(), Some(50));
102 assert_eq!(items[2].method(), Method::TOTP);
103 assert_eq!(items[2].algorithm(), Algorithm::SHA512);
104 assert_eq!(items[2].digits(), Some(8));
105 assert_eq!(items[2].counter(), None);
106
107 assert_eq!(items[3].account(), "James");
108 assert_eq!(items[3].issuer(), "Issuu");
109 assert_eq!(items[3].secret(), "YOOMIXWS5GN6RTBPUFFWKTW5M4");
110 assert_eq!(items[3].period(), None);
111 assert_eq!(items[3].method(), Method::HOTP);
112 assert_eq!(items[3].algorithm(), Algorithm::SHA1);
113 assert_eq!(items[3].digits(), Some(6));
114 assert_eq!(items[3].counter(), Some(1));
115
116 assert_eq!(items[4].account(), "Benjamin");
117 assert_eq!(items[4].issuer(), "Air Canada");
118 assert_eq!(items[4].secret(), "KUVJJOM753IHTNDSZVCNKL7GII");
119 assert_eq!(items[4].period(), None);
120 assert_eq!(items[4].method(), Method::HOTP);
121 assert_eq!(items[4].algorithm(), Algorithm::SHA256);
122 assert_eq!(items[4].digits(), Some(7));
123 assert_eq!(items[4].counter(), Some(50));
124
125 assert_eq!(items[5].account(), "Mason");
126 assert_eq!(items[5].issuer(), "WWE");
127 assert_eq!(items[5].secret(), "5VAML3X35THCEBVRLV24CGBKOY");
128 assert_eq!(items[5].period(), None);
129 assert_eq!(items[5].method(), Method::HOTP);
130 assert_eq!(items[5].algorithm(), Algorithm::SHA512);
131 assert_eq!(items[5].digits(), Some(8));
132 assert_eq!(items[5].counter(), Some(10300));
133
134 assert_eq!(items[6].account(), "Sophia");
135 assert_eq!(items[6].issuer(), "Boeing");
136 assert_eq!(items[6].secret(), "JRZCL47CMXVOQMNPZR2F7J4RGI");
137 assert_eq!(items[6].period(), Some(30));
138 assert_eq!(items[6].method(), Method::Steam);
139 assert_eq!(items[6].algorithm(), Algorithm::SHA1);
140 assert_eq!(items[6].digits(), Some(5));
141 assert_eq!(items[6].counter(), None);
142 }
143}