Skip to main content

authenticator/models/
providers.rs

1use anyhow::Result;
2use gtk::{gio, glib, prelude::*, subclass::prelude::*};
3
4use super::{Account, Algorithm, Method, OTP, Provider, ProviderPatch};
5
6mod imp {
7    use std::cell::{Cell, RefCell};
8
9    use super::*;
10
11    #[derive(Default)]
12    pub struct ProvidersModel(pub RefCell<Vec<Provider>>, pub Cell<bool>);
13
14    #[glib::object_subclass]
15    impl ObjectSubclass for ProvidersModel {
16        const NAME: &'static str = "ProvidersModel";
17        type Type = super::ProvidersModel;
18        type Interfaces = (gio::ListModel,);
19    }
20    impl ObjectImpl for ProvidersModel {}
21    impl ListModelImpl for ProvidersModel {
22        fn item_type(&self) -> glib::Type {
23            Provider::static_type()
24        }
25        fn n_items(&self) -> u32 {
26            self.0.borrow().len() as u32
27        }
28        fn item(&self, position: u32) -> Option<glib::Object> {
29            self.0
30                .borrow()
31                .get(position as usize)
32                .map(|o| o.clone().upcast::<glib::Object>())
33        }
34    }
35}
36
37glib::wrapper! {
38    pub struct ProvidersModel(ObjectSubclass<imp::ProvidersModel>)
39        @implements gio::ListModel;
40}
41
42impl ProvidersModel {
43    #[allow(clippy::too_many_arguments)]
44    pub fn find_or_create(
45        &self,
46        name: &str,
47        period: Option<u32>,
48        method: Method,
49        website: Option<String>,
50        algorithm: Algorithm,
51        digits: Option<u32>,
52        default_counter: Option<u32>,
53        help_url: Option<String>,
54        image_uri: Option<String>,
55    ) -> Result<Provider> {
56        let provider = match self.find_by_name(name) {
57            Some(p) => {
58                // Update potenitally different properties than what we have in the pre-shipped
59                // database Note this does a comparaison first to avoid a
60                // uselesss rewrite
61                p.update(&ProviderPatch {
62                    name: name.to_owned(),
63                    website,
64                    help_url,
65                    image_uri,
66                    period: period.unwrap_or_else(|| p.period()) as i32,
67                    digits: digits.unwrap_or_else(|| p.digits()) as i32,
68                    default_counter: default_counter.unwrap_or_else(|| p.default_counter()) as i32,
69                    algorithm: algorithm.to_string(),
70                    method: method.to_string(),
71                    is_backup_restore: true,
72                })?;
73                p
74            }
75            None => {
76                let p = Provider::create(
77                    name,
78                    period.unwrap_or(OTP::DEFAULT_PERIOD),
79                    algorithm,
80                    website,
81                    method,
82                    digits.unwrap_or(OTP::DEFAULT_DIGITS),
83                    default_counter.unwrap_or(OTP::DEFAULT_COUNTER),
84                    help_url,
85                    image_uri,
86                )?;
87                self.append(&p);
88                p
89            }
90        };
91        Ok(provider)
92    }
93
94    fn find_by_name(&self, name: &str) -> Option<Provider> {
95        for pos in 0..self.n_items() {
96            let provider = self.item(pos).and_downcast::<Provider>().unwrap();
97            if provider.name() == name {
98                return Some(provider);
99            }
100        }
101        None
102    }
103
104    pub fn find_by_id(&self, id: u32) -> Option<Provider> {
105        for pos in 0..self.n_items() {
106            let provider = self.item(pos).and_downcast::<Provider>().unwrap();
107            if provider.id() == id {
108                return Some(provider);
109            }
110        }
111        None
112    }
113
114    pub fn has_providers(&self) -> bool {
115        let mut found = false;
116        for pos in 0..self.n_items() {
117            let provider = self.item(pos).and_downcast::<Provider>().unwrap();
118            if provider.has_accounts() {
119                found = true;
120                break;
121            }
122        }
123        found
124    }
125
126    #[allow(deprecated)]
127    pub fn completion_model(&self) -> gtk::ListStore {
128        let store = gtk::ListStore::new(&[u32::static_type(), String::static_type()]);
129        for pos in 0..self.n_items() {
130            let obj = self.item(pos).unwrap();
131            let provider = obj.downcast_ref::<Provider>().unwrap();
132            store.set(
133                &store.append(),
134                &[(0, &provider.id()), (1, &provider.name())],
135            );
136        }
137        store
138    }
139
140    pub fn append(&self, provider: &Provider) {
141        let pos = {
142            let mut data = self.imp().0.borrow_mut();
143            data.push(provider.clone());
144            (data.len() - 1) as u32
145        };
146        self.items_changed(pos, 0, 1);
147    }
148
149    fn splice(&self, providers: &[Provider]) {
150        let len = providers.len();
151        let pos = {
152            let mut data = self.imp().0.borrow_mut();
153            let pos = data.len();
154            data.extend_from_slice(providers);
155            pos as u32
156        };
157        self.items_changed(pos, 0, len as u32);
158    }
159
160    pub fn delete_provider(&self, provider: &Provider) {
161        let mut provider_pos = None;
162        for pos in 0..self.n_items() {
163            let p = self.item(pos).and_downcast::<Provider>().unwrap();
164            if p.id() == provider.id() {
165                provider_pos = Some(pos);
166                break;
167            }
168        }
169        if let Some(pos) = provider_pos {
170            {
171                let mut data = self.imp().0.borrow_mut();
172                data.remove(pos as usize);
173            }
174            self.items_changed(pos, 1, 0);
175        }
176    }
177
178    pub fn add_account(&self, account: &Account, provider: &Provider) {
179        let mut found = false;
180        for pos in 0..self.n_items() {
181            let obj = self.item(pos).unwrap();
182            let p = obj.downcast_ref::<Provider>().unwrap();
183            if p.id() == provider.id() {
184                found = true;
185                p.add_account(account);
186                break;
187            }
188        }
189        if !found {
190            provider.add_account(account);
191            self.append(provider);
192        }
193    }
194
195    pub fn find_accounts(&self, terms: &[String]) -> Vec<Account> {
196        let mut results = vec![];
197
198        for pos in 0..self.n_items() {
199            let obj = self.item(pos).unwrap();
200            let provider = obj.downcast_ref::<Provider>().unwrap();
201            let accounts = provider.find_accounts(terms);
202            results.extend(accounts);
203        }
204        results
205    }
206
207    /// Check whether the model was loaded from the database
208    pub fn is_loaded(&self) -> bool {
209        self.imp().1.get()
210    }
211
212    pub fn load(&self) {
213        if self.is_loaded() {
214            return;
215        }
216        tracing::info!("Loading providers");
217        // fill in the providers from the database
218        let providers = Provider::load()
219            .expect("Failed to load providers from the database")
220            .collect::<Vec<_>>();
221        self.splice(&providers);
222        self.imp().1.set(true);
223    }
224}
225
226impl Default for ProvidersModel {
227    fn default() -> Self {
228        glib::Object::new()
229    }
230}