lockbook_shared/
core_ops.rs

1use db_rs::LookupTable;
2use std::collections::HashSet;
3
4use hmac::{Mac, NewMac};
5use libsecp256k1::PublicKey;
6use tracing::debug;
7use uuid::Uuid;
8
9use crate::access_info::{UserAccessInfo, UserAccessMode};
10use crate::account::Account;
11use crate::crypto::{AESKey, DecryptedDocument, EncryptedDocument};
12use crate::document_repo::DocumentService;
13use crate::file::{File, Share, ShareMode};
14use crate::file_like::FileLike;
15use crate::file_metadata::{FileMetadata, FileType, Owner};
16use crate::lazy::LazyTree;
17use crate::secret_filename::{HmacSha256, SecretFileName};
18use crate::signed_file::SignedFile;
19use crate::staged::{StagedTree, StagedTreeLike};
20use crate::tree_like::{TreeLike, TreeLikeMut};
21use crate::{compression_service, symkey, validate, SharedErrorKind, SharedResult};
22
23pub type TreeWithOp<Staged> = LazyTree<StagedTree<Staged, Option<SignedFile>>>;
24pub type TreeWithOps<Staged> = LazyTree<StagedTree<Staged, Vec<SignedFile>>>;
25
26impl<T> LazyTree<T>
27where
28    T: TreeLike<F = SignedFile>,
29{
30    /// convert FileMetadata into File. fields have been decrypted, public keys replaced with usernames, deleted files filtered out, etc.
31    pub fn decrypt(
32        &mut self, account: &Account, id: &Uuid, public_key_cache: &mut LookupTable<Owner, String>,
33    ) -> SharedResult<File> {
34        let meta = self.find(id)?.clone();
35        let file_type = meta.file_type();
36        let last_modified = meta.timestamped_value.timestamp as u64;
37        let name = self.name_using_links(id, account)?;
38        let parent = self.parent_using_links(id)?;
39        let last_modified_by = account.username.clone();
40        let id = *id;
41
42        let mut shares = Vec::new();
43        for user_access_key in meta.user_access_keys() {
44            if user_access_key.encrypted_by == user_access_key.encrypted_for {
45                continue;
46            }
47            let mode = match user_access_key.mode {
48                UserAccessMode::Read => ShareMode::Read,
49                UserAccessMode::Write => ShareMode::Write,
50                UserAccessMode::Owner => continue,
51            };
52            shares.push(Share {
53                mode,
54                shared_by: if user_access_key.encrypted_by == account.public_key() {
55                    account.username.clone()
56                } else {
57                    public_key_cache
58                        .get()
59                        .get(&Owner(user_access_key.encrypted_by))
60                        .cloned()
61                        .unwrap_or_else(|| String::from("<unknown>"))
62                },
63                shared_with: if user_access_key.encrypted_for == account.public_key() {
64                    account.username.clone()
65                } else {
66                    public_key_cache
67                        .get()
68                        .get(&Owner(user_access_key.encrypted_for))
69                        .cloned()
70                        .unwrap_or_else(|| String::from("<unknown>"))
71                },
72            });
73        }
74
75        Ok(File { id, parent, name, file_type, last_modified, last_modified_by, shares })
76    }
77
78    /// convert FileMetadata into File. fields have been decrypted, public keys replaced with usernames, deleted files filtered out, etc.
79    pub fn decrypt_all<I>(
80        &mut self, account: &Account, ids: I, public_key_cache: &mut LookupTable<Owner, String>,
81        skip_invisible: bool,
82    ) -> SharedResult<Vec<File>>
83    where
84        I: Iterator<Item = Uuid>,
85    {
86        let mut files: Vec<File> = Vec::new();
87
88        for id in ids {
89            if skip_invisible && self.is_invisible_id(id)? {
90                continue;
91            }
92
93            let finalized = self.decrypt(account, &id, public_key_cache)?;
94            files.push(finalized);
95        }
96
97        Ok(files)
98    }
99
100    fn is_invisible_id(&mut self, id: Uuid) -> SharedResult<bool> {
101        Ok(self.find(&id)?.is_link()
102            || self.calculate_deleted(&id)?
103            || self.in_pending_share(&id)?)
104    }
105
106    pub fn create_op(
107        &mut self, id: Uuid, key: AESKey, parent: &Uuid, name: &str, file_type: FileType,
108        account: &Account,
109    ) -> SharedResult<(SignedFile, Uuid)> {
110        validate::file_name(name)?;
111
112        if self.maybe_find(parent).is_none() {
113            return Err(SharedErrorKind::FileParentNonexistent.into());
114        }
115        let parent_owner = self.find(parent)?.owner().0;
116        let parent_key = self.decrypt_key(parent, account)?;
117        let file =
118            FileMetadata::create(id, key, &parent_owner, *parent, &parent_key, name, file_type)?
119                .sign(account)?;
120        let id = *file.id();
121
122        debug!("new {:?} with id: {}", file_type, id);
123        Ok((file, id))
124    }
125
126    pub fn rename_op(
127        &mut self, id: &Uuid, name: &str, account: &Account,
128    ) -> SharedResult<SignedFile> {
129        let mut file = self.find(id)?.timestamped_value.value.clone();
130
131        validate::file_name(name)?;
132        if self.maybe_find(file.parent()).is_none() {
133            return Err(SharedErrorKind::InsufficientPermission.into());
134        }
135        let parent_key = self.decrypt_key(file.parent(), account)?;
136        let key = self.decrypt_key(id, account)?;
137        file.name = SecretFileName::from_str(name, &key, &parent_key)?;
138        let file = file.sign(account)?;
139
140        Ok(file)
141    }
142
143    pub fn move_op(
144        &mut self, id: &Uuid, new_parent: &Uuid, account: &Account,
145    ) -> SharedResult<Vec<SignedFile>> {
146        let mut file = self.find(id)?.timestamped_value.value.clone();
147        if self.maybe_find(new_parent).is_none() {
148            return Err(SharedErrorKind::FileParentNonexistent.into());
149        }
150        let key = self.decrypt_key(id, account)?;
151        let parent_key = self.decrypt_key(new_parent, account)?;
152        let owner = self.find(new_parent)?.owner();
153        file.owner = owner;
154        file.parent = *new_parent;
155        file.folder_access_key = symkey::encrypt(&parent_key, &key)?;
156        file.name = SecretFileName::from_str(&self.name(id, account)?, &key, &parent_key)?;
157        let file = file.sign(account)?;
158
159        let mut result = vec![file];
160        for id in self.descendants(id)? {
161            if self.calculate_deleted(&id)? {
162                continue;
163            }
164            let mut descendant = self.find(&id)?.timestamped_value.value.clone();
165            descendant.owner = owner;
166            result.push(descendant.sign(account)?);
167        }
168
169        Ok(result)
170    }
171
172    pub fn delete_op(&self, id: &Uuid, account: &Account) -> SharedResult<SignedFile> {
173        let mut file = self.find(id)?.timestamped_value.value.clone();
174
175        file.is_deleted = true;
176        let file = file.sign(account)?;
177
178        Ok(file)
179    }
180
181    pub fn add_share_op(
182        &mut self, id: Uuid, sharee: Owner, mode: ShareMode, account: &Account,
183    ) -> SharedResult<SignedFile> {
184        let owner = Owner(account.public_key());
185        let access_mode = match mode {
186            ShareMode::Write => UserAccessMode::Write,
187            ShareMode::Read => UserAccessMode::Read,
188        };
189        if self.calculate_deleted(&id)? {
190            return Err(SharedErrorKind::FileNonexistent.into());
191        }
192        let id =
193            if let FileType::Link { target } = self.find(&id)?.file_type() { target } else { id };
194        let mut file = self.find(&id)?.timestamped_value.value.clone();
195        validate::not_root(&file)?;
196        if mode == ShareMode::Write && file.owner.0 != owner.0 {
197            return Err(SharedErrorKind::InsufficientPermission.into());
198        }
199        // check for and remove duplicate shares
200        let mut found = false;
201        for user_access in &mut file.user_access_keys {
202            if user_access.encrypted_for == sharee.0 {
203                found = true;
204                if user_access.mode == access_mode && !user_access.deleted {
205                    return Err(SharedErrorKind::DuplicateShare.into());
206                }
207            }
208        }
209        if found {
210            file.user_access_keys
211                .retain(|k| k.encrypted_for != sharee.0);
212        }
213        file.user_access_keys.push(UserAccessInfo::encrypt(
214            account,
215            &owner.0,
216            &sharee.0,
217            &self.decrypt_key(&id, account)?,
218            access_mode,
219        )?);
220        let file = file.sign(account)?;
221
222        Ok(file)
223    }
224
225    pub fn delete_share_op(
226        &mut self, id: &Uuid, maybe_encrypted_for: Option<PublicKey>, account: &Account,
227    ) -> SharedResult<Vec<SignedFile>> {
228        let mut result = Vec::new();
229        let mut file = self.find(id)?.timestamped_value.value.clone();
230
231        let mut found = false;
232        for key in file.user_access_keys.iter_mut() {
233            if let Some(encrypted_for) = maybe_encrypted_for {
234                if !key.deleted && key.encrypted_for == encrypted_for {
235                    found = true;
236                    key.deleted = true;
237                }
238            } else if !key.deleted {
239                found = true;
240                key.deleted = true;
241            }
242        }
243        if !found {
244            return Err(SharedErrorKind::ShareNonexistent.into());
245        }
246        result.push(file.sign(account)?);
247
248        // delete any links pointing to file
249        if let Some(encrypted_for) = maybe_encrypted_for {
250            if encrypted_for == account.public_key() {
251                if let Some(link) = self.linked_by(id)? {
252                    let mut link = self.find(&link)?.timestamped_value.value.clone();
253                    link.is_deleted = true;
254                    result.push(link.sign(account)?);
255                }
256            }
257        }
258
259        Ok(result)
260    }
261
262    pub fn read_document(
263        &mut self, d: &impl DocumentService, id: &Uuid, account: &Account,
264    ) -> SharedResult<DecryptedDocument> {
265        if self.calculate_deleted(id)? {
266            return Err(SharedErrorKind::FileNonexistent.into());
267        }
268
269        let meta = self.find(id)?;
270
271        validate::is_document(meta)?;
272        if meta.document_hmac().is_none() {
273            return Ok(vec![]);
274        }
275
276        let maybe_encrypted_document = match d.maybe_get(meta.id(), meta.document_hmac())? {
277            Some(local) => Some(local),
278            None => d.maybe_get(meta.id(), meta.document_hmac())?,
279        };
280        let doc = match maybe_encrypted_document {
281            Some(doc) => self.decrypt_document(id, &doc, account)?,
282            None => return Err(SharedErrorKind::FileNonexistent.into()),
283        };
284
285        Ok(doc)
286    }
287
288    pub fn update_document_op(
289        &mut self, id: &Uuid, document: &[u8], account: &Account,
290    ) -> SharedResult<(SignedFile, EncryptedDocument)> {
291        let id = match self.find(id)?.file_type() {
292            FileType::Document | FileType::Folder => *id,
293            FileType::Link { target } => target,
294        };
295        let mut file: FileMetadata = self.find(&id)?.timestamped_value.value.clone();
296        validate::is_document(&file)?;
297        let key = self.decrypt_key(&id, account)?;
298        let hmac = {
299            let mut mac =
300                HmacSha256::new_from_slice(&key).map_err(SharedErrorKind::HmacCreationError)?;
301            mac.update(document);
302            mac.finalize().into_bytes()
303        }
304        .into();
305        file.document_hmac = Some(hmac);
306        let file = file.sign(account)?;
307        let document = compression_service::compress(document)?;
308        let document = symkey::encrypt(&key, &document)?;
309
310        Ok((file, document))
311    }
312}
313
314impl<Base, Local, Staged> LazyTree<Staged>
315where
316    Staged: StagedTreeLike<Base = Base, Staged = Local, F = SignedFile> + TreeLikeMut,
317    Base: TreeLike<F = Staged::F>,
318    Local: TreeLikeMut<F = Staged::F>,
319{
320    pub fn create_unvalidated(
321        &mut self, id: Uuid, key: AESKey, parent: &Uuid, name: &str, file_type: FileType,
322        account: &Account,
323    ) -> SharedResult<Uuid> {
324        let (op, id) = self.create_op(id, key, parent, name, file_type, account)?;
325        self.stage_and_promote(Some(op))?;
326        Ok(id)
327    }
328
329    pub fn create(
330        &mut self, id: Uuid, key: AESKey, parent: &Uuid, name: &str, file_type: FileType,
331        account: &Account,
332    ) -> SharedResult<Uuid> {
333        if self.calculate_deleted(parent)? {
334            return Err(SharedErrorKind::FileParentNonexistent.into());
335        }
336
337        let (op, id) = self.create_op(id, key, parent, name, file_type, account)?;
338        self.stage_validate_and_promote(Some(op), Owner(account.public_key()))?;
339        Ok(id)
340    }
341
342    pub fn rename_unvalidated(
343        &mut self, id: &Uuid, name: &str, account: &Account,
344    ) -> SharedResult<()> {
345        let op = self.rename_op(id, name, account)?;
346        self.stage_and_promote(Some(op))?;
347        Ok(())
348    }
349
350    pub fn rename(&mut self, id: &Uuid, name: &str, account: &Account) -> SharedResult<()> {
351        let op = self.rename_op(id, name, account)?;
352        self.stage_validate_and_promote(Some(op), Owner(account.public_key()))?;
353        Ok(())
354    }
355
356    pub fn move_unvalidated(
357        &mut self, id: &Uuid, new_parent: &Uuid, account: &Account,
358    ) -> SharedResult<()> {
359        let op = self.move_op(id, new_parent, account)?;
360        self.stage_and_promote(op)?;
361        Ok(())
362    }
363
364    pub fn move_file(
365        &mut self, id: &Uuid, new_parent: &Uuid, account: &Account,
366    ) -> SharedResult<()> {
367        if self.maybe_find(new_parent).is_none() || self.calculate_deleted(new_parent)? {
368            return Err(SharedErrorKind::FileParentNonexistent.into());
369        }
370        let op = self.move_op(id, new_parent, account)?;
371        self.stage_validate_and_promote(op, Owner(account.public_key()))?;
372        Ok(())
373    }
374
375    pub fn delete_unvalidated(&mut self, id: &Uuid, account: &Account) -> SharedResult<()> {
376        let op = self.delete_op(id, account)?;
377        self.stage_and_promote(Some(op))?;
378        Ok(())
379    }
380
381    pub fn delete(&mut self, id: &Uuid, account: &Account) -> SharedResult<()> {
382        let op = self.delete_op(id, account)?;
383        self.stage_validate_and_promote(Some(op), Owner(account.public_key()))?;
384        Ok(())
385    }
386
387    pub fn add_share_unvalidated(
388        &mut self, id: Uuid, sharee: Owner, mode: ShareMode, account: &Account,
389    ) -> SharedResult<()> {
390        let op = self.add_share_op(id, sharee, mode, account)?;
391        self.stage_and_promote(Some(op))?;
392        Ok(())
393    }
394
395    pub fn add_share(
396        &mut self, id: Uuid, sharee: Owner, mode: ShareMode, account: &Account,
397    ) -> SharedResult<()> {
398        let op = self.add_share_op(id, sharee, mode, account)?;
399        self.stage_validate_and_promote(Some(op), Owner(account.public_key()))?;
400        Ok(())
401    }
402
403    pub fn delete_share_unvalidated(
404        &mut self, id: &Uuid, maybe_encrypted_for: Option<PublicKey>, account: &Account,
405    ) -> SharedResult<()> {
406        let op = self.delete_share_op(id, maybe_encrypted_for, account)?;
407        self.stage_and_promote(op)?;
408        Ok(())
409    }
410
411    pub fn delete_share(
412        &mut self, id: &Uuid, maybe_encrypted_for: Option<PublicKey>, account: &Account,
413    ) -> SharedResult<()> {
414        let op = self.delete_share_op(id, maybe_encrypted_for, account)?;
415        self.stage_validate_and_promote(op, Owner(account.public_key()))?;
416        Ok(())
417    }
418
419    pub fn update_document_unvalidated(
420        &mut self, id: &Uuid, document: &[u8], account: &Account,
421    ) -> SharedResult<EncryptedDocument> {
422        let (op, document) = self.update_document_op(id, document, account)?;
423        self.stage_and_promote(Some(op))?;
424        Ok(document)
425    }
426
427    pub fn update_document(
428        &mut self, id: &Uuid, document: &[u8], account: &Account,
429    ) -> SharedResult<EncryptedDocument> {
430        let (op, document) = self.update_document_op(id, document, account)?;
431        self.stage_validate_and_promote(Some(op), Owner(account.public_key()))?;
432        Ok(document)
433    }
434
435    pub fn delete_unreferenced_file_versions(
436        &self, docs: &impl DocumentService,
437    ) -> SharedResult<()> {
438        let base_files = self.tree.base().all_files()?.into_iter();
439        let local_files = self.tree.all_files()?.into_iter();
440        let file_hmacs = base_files
441            .chain(local_files)
442            .filter_map(|f| f.document_hmac().map(|hmac| (f.id(), hmac)))
443            .collect::<HashSet<_>>();
444        docs.retain(file_hmacs)
445    }
446}