lb_rs/model/
core_ops.rs

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