t_rust_less_lib/service/
local.rs

1use super::pw_generator::{generate_chars, generate_words};
2use super::synchronizer::Synchronizer;
3use crate::api::{ClipboardProviding, Event, EventData, EventHub, PasswordGeneratorParam, StoreConfig};
4use crate::block_store::StoreError;
5use crate::clipboard::{Clipboard, ClipboardCommon};
6use crate::secrets_store::{open_secrets_store, SecretStoreResult, SecretsStore};
7use crate::service::config::{read_config, write_config, Config};
8use crate::service::error::{ServiceError, ServiceResult};
9#[cfg(any(unix, windows))]
10use crate::service::secrets_provider::SecretsProvider;
11use crate::service::{ClipboardControl, TrustlessService};
12use chrono::{DateTime, Utc};
13use log::{error, info};
14use rand::{distributions, thread_rng, Rng};
15use std::collections::{HashMap, VecDeque};
16use std::sync::{Arc, Mutex, RwLock};
17use std::time::Duration;
18
19enum ClipboardHolder {
20  Empty,
21  Providing(Clipboard),
22}
23
24impl ClipboardControl for ClipboardHolder {
25  fn is_done(&self) -> ServiceResult<bool> {
26    match self {
27      ClipboardHolder::Empty => Ok(true),
28      ClipboardHolder::Providing(clipboard) => Ok(!clipboard.is_open()),
29    }
30  }
31
32  fn currently_providing(&self) -> ServiceResult<Option<ClipboardProviding>> {
33    match self {
34      ClipboardHolder::Empty => Ok(None),
35      ClipboardHolder::Providing(clipboard) => Ok(clipboard.currently_providing()),
36    }
37  }
38
39  fn provide_next(&self) -> ServiceResult<()> {
40    if let ClipboardHolder::Providing(clipboard) = &self {
41      clipboard.provide_next();
42    }
43    Ok(())
44  }
45
46  fn destroy(&self) -> ServiceResult<()> {
47    if let ClipboardHolder::Providing(clipboard) = &self {
48      clipboard.destroy();
49    }
50    Ok(())
51  }
52}
53
54struct LocalEventQueue {
55  last_id: u64,
56  limit: usize,
57  queue: VecDeque<Event>,
58}
59
60impl LocalEventQueue {
61  fn new(limit: usize) -> Self {
62    LocalEventQueue {
63      last_id: 0,
64      limit,
65      queue: VecDeque::with_capacity(limit),
66    }
67  }
68
69  fn queue(&mut self, data: EventData) {
70    if self.queue.len() >= self.limit {
71      self.queue.pop_front();
72    }
73    self.last_id += 1;
74    self.queue.push_back(Event { id: self.last_id, data });
75  }
76
77  fn poll(&self, last_id: u64) -> Vec<Event> {
78    match self.queue.iter().position(|e| e.id > last_id) {
79      Some(start) => self.queue.iter().skip(start).cloned().collect(),
80      None => vec![],
81    }
82  }
83}
84
85struct LocalEventHub {
86  event_queue: RwLock<LocalEventQueue>,
87}
88
89impl LocalEventHub {
90  fn new(limit: usize) -> Self {
91    LocalEventHub {
92      event_queue: RwLock::new(LocalEventQueue::new(limit)),
93    }
94  }
95
96  fn poll_events(&self, last_id: u64) -> ServiceResult<Vec<Event>> {
97    let event_queue = self.event_queue.read()?;
98
99    Ok(event_queue.poll(last_id))
100  }
101}
102
103impl EventHub for LocalEventHub {
104  fn send(&self, event: EventData) {
105    match self.event_queue.write() {
106      Ok(mut event_queue) => event_queue.queue(event),
107      Err(e) => {
108        error!("Queue event failed: {e}");
109      }
110    };
111  }
112}
113
114pub struct LocalTrustlessService {
115  config: RwLock<Config>,
116  opened_stores: RwLock<HashMap<String, Arc<dyn SecretsStore>>>,
117  synchronizers: Mutex<Vec<Synchronizer>>,
118  clipboard: RwLock<Arc<ClipboardHolder>>,
119  event_hub: Arc<LocalEventHub>,
120}
121
122impl LocalTrustlessService {
123  pub fn new() -> ServiceResult<LocalTrustlessService> {
124    let config = read_config()?.unwrap_or_default();
125
126    Ok(LocalTrustlessService {
127      config: RwLock::new(config),
128      opened_stores: RwLock::new(HashMap::new()),
129      synchronizers: Mutex::new(vec![]),
130      clipboard: RwLock::new(Arc::new(ClipboardHolder::Empty)),
131      event_hub: Arc::new(LocalEventHub::new(100)),
132    })
133  }
134}
135
136impl TrustlessService for LocalTrustlessService {
137  fn list_stores(&self) -> ServiceResult<Vec<StoreConfig>> {
138    let config = self.config.read()?;
139
140    Ok(config.stores.values().cloned().collect())
141  }
142
143  fn upsert_store_config(&self, mut store_config: StoreConfig) -> ServiceResult<()> {
144    let mut config = self.config.write()?;
145
146    if store_config.client_id.is_empty() {
147      store_config.client_id = self.generate_id()?;
148    }
149
150    if config.default_store.is_none() {
151      config.default_store = Some(store_config.name.to_string());
152    }
153    config.stores.insert(store_config.name.to_string(), store_config);
154    write_config(&config)?;
155
156    Ok(())
157  }
158
159  fn delete_store_config(&self, name: &str) -> ServiceResult<()> {
160    let mut config = self.config.write()?;
161
162    if config.stores.remove(name).is_some() {
163      write_config(&config)?;
164    }
165
166    Ok(())
167  }
168
169  fn open_store(&self, name: &str) -> SecretStoreResult<Arc<dyn SecretsStore>> {
170    {
171      let opened_stores = self.opened_stores.read()?;
172
173      if let Some(store) = opened_stores.get(name) {
174        return Ok(store.clone());
175      }
176    }
177    let mut opened_stores = self.opened_stores.write()?;
178    let config = self.config.read()?;
179    let store_config = config
180      .stores
181      .get(name)
182      .ok_or_else(|| StoreError::StoreNotFound(name.to_string()))?;
183    let (store, maybe_sync_block_store) = open_secrets_store(
184      name,
185      &store_config.store_url,
186      store_config.remote_url.as_deref(),
187      &store_config.client_id,
188      Duration::from_secs(store_config.autolock_timeout_secs),
189      self.event_hub.clone(),
190    )?;
191
192    if let Some(sync_block_store) = maybe_sync_block_store {
193      self.synchronizers.lock()?.push(Synchronizer::new(
194        store.clone(),
195        sync_block_store,
196        chrono::Duration::seconds(store_config.sync_interval_sec as i64),
197      ));
198    }
199
200    opened_stores.insert(name.to_string(), store.clone());
201
202    Ok(store)
203  }
204
205  fn get_default_store(&self) -> ServiceResult<Option<String>> {
206    let config = self.config.read()?;
207
208    Ok(config.default_store.to_owned())
209  }
210
211  fn set_default_store(&self, name: &str) -> ServiceResult<()> {
212    let mut config = self.config.write()?;
213
214    if !config.stores.contains_key(name) {
215      return Err(ServiceError::StoreNotFound(name.to_string()));
216    }
217
218    config.default_store = Some(name.to_string());
219    write_config(&config)?;
220
221    Ok(())
222  }
223
224  fn secret_to_clipboard(
225    &self,
226    store_name: &str,
227    block_id: &str,
228    properties: &[&str],
229  ) -> ServiceResult<Arc<dyn ClipboardControl>> {
230    #[cfg(any(unix, windows))]
231    {
232      let store = self.open_store(store_name)?;
233      let secret_version = store.get_version(block_id)?;
234      let secret_provider =
235        SecretsProvider::new(store_name.to_string(), block_id.to_string(), secret_version, properties);
236      let mut clipboard = self.clipboard.write()?;
237
238      clipboard.destroy()?;
239
240      info!("Providing {} for {} in {}", properties.join(","), block_id, store_name);
241
242      let next_clipboard = Arc::new(ClipboardHolder::Providing(Clipboard::new(
243        secret_provider,
244        self.event_hub.clone(),
245      )?));
246      *clipboard = next_clipboard.clone();
247
248      Ok(next_clipboard)
249    }
250    #[cfg(not(any(unix, windows)))]
251    {
252      Err(ServiceError::NotAvailable)
253    }
254  }
255
256  fn poll_events(&self, last_id: u64) -> ServiceResult<Vec<Event>> {
257    self.event_hub.poll_events(last_id)
258  }
259
260  fn generate_id(&self) -> ServiceResult<String> {
261    let rng = thread_rng();
262
263    Ok(
264      rng
265        .sample_iter(distributions::Alphanumeric)
266        .map(char::from)
267        .take(64)
268        .collect::<String>(),
269    )
270  }
271
272  fn generate_password(&self, param: PasswordGeneratorParam) -> ServiceResult<String> {
273    match &param {
274      PasswordGeneratorParam::Chars(params) => Ok(generate_chars(params)),
275      PasswordGeneratorParam::Words(params) => Ok(generate_words(params)),
276    }
277  }
278
279  fn check_autolock(&self) {
280    let opened_stores = match self.opened_stores.read() {
281      Ok(opened_stores) => opened_stores,
282      Err(err) => {
283        error!("Failed locking opened stores: {err}");
284        return;
285      }
286    };
287
288    for (name, secrets_store) in opened_stores.iter() {
289      let status = match secrets_store.status() {
290        Ok(status) => status,
291        Err(error) => {
292          error!("Autolocker was unable to query status: {error}");
293          continue;
294        }
295      };
296
297      if let Some(autolock_at) = status.autolock_at {
298        if autolock_at < Utc::now().into() {
299          info!("Autolocking {name}");
300          if let Err(error) = secrets_store.lock() {
301            error!("Autolocker was unable to lock store: {error}");
302          }
303        }
304      }
305    }
306  }
307
308  fn needs_synchronization(&self) -> bool {
309    if let Ok(config) = self.config.read() {
310      config
311        .stores
312        .iter()
313        .any(|(_, store_config)| store_config.remote_url.is_some())
314    } else {
315      false
316    }
317  }
318
319  fn synchronize(&self) -> Option<DateTime<Utc>> {
320    match self.synchronizers.lock() {
321      Ok(mut synchronizers) => {
322        let mut result = None;
323        for synchronizer in synchronizers.iter_mut() {
324          if let Err(err) = synchronizer.synchronize() {
325            error!("Synchronization failed: {err}");
326          }
327          let next = synchronizer.next_run();
328          result = match result {
329            Some(prev) if prev > next => Some(next),
330            Some(prev) => Some(prev),
331            None => Some(next),
332          };
333        }
334        result
335      }
336      Err(err) => {
337        error!("Synchronization lock failed: {err}");
338        None
339      }
340    }
341  }
342}
343
344impl std::fmt::Debug for LocalTrustlessService {
345  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346    write!(f, "Local Trustless service")
347  }
348}