lb_rs/model/
file_metadata.rs

1use std::fmt::{self, Debug, Formatter};
2use std::hash::{Hash, Hasher};
3use std::str::FromStr;
4
5use libsecp256k1::PublicKey;
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use super::access_info::{EncryptedFolderAccessKey, UserAccessInfo, UserAccessMode};
10use super::account::Account;
11use super::clock::get_time;
12use super::errors::LbResult;
13use crate::model::crypto::AESKey;
14use crate::model::file_like::FileLike;
15use crate::model::secret_filename::SecretFileName;
16use crate::model::signed_file::SignedFile;
17use crate::model::{pubkey, symkey};
18use crate::service::keychain::Keychain;
19
20pub type DocumentHmac = [u8; 32];
21
22#[derive(Serialize, Deserialize, Clone, Debug)]
23pub struct FileMetadata {
24    pub id: Uuid,
25    pub file_type: FileType,
26    pub parent: Uuid,
27    pub name: SecretFileName,
28    pub owner: Owner,
29    pub is_deleted: bool,
30    pub document_hmac: Option<DocumentHmac>,
31    pub user_access_keys: Vec<UserAccessInfo>,
32    pub folder_access_key: EncryptedFolderAccessKey,
33}
34
35impl FileMetadata {
36    pub fn create_root(account: &Account) -> LbResult<Self> {
37        let id = Uuid::new_v4();
38        let key = symkey::generate_key();
39        let pub_key = account.public_key();
40
41        Ok(FileMetadata {
42            id,
43            file_type: FileType::Folder,
44            parent: id,
45            name: SecretFileName::from_str(&account.username, &key, &key)?,
46            owner: Owner(pub_key),
47            is_deleted: false,
48            document_hmac: None,
49            user_access_keys: vec![UserAccessInfo::encrypt(
50                account,
51                &pub_key,
52                &pub_key,
53                &key,
54                UserAccessMode::Write,
55            )?],
56            folder_access_key: symkey::encrypt(&key, &key)?,
57        })
58    }
59
60    pub fn create(
61        id: Uuid, key: AESKey, owner: &PublicKey, parent: Uuid, parent_key: &AESKey, name: &str,
62        file_type: FileType,
63    ) -> LbResult<Self> {
64        Ok(FileMetadata {
65            id,
66            file_type,
67            parent,
68            name: SecretFileName::from_str(name, &key, parent_key)?,
69            owner: Owner(*owner),
70            is_deleted: false,
71            document_hmac: None,
72            user_access_keys: Default::default(),
73            folder_access_key: symkey::encrypt(parent_key, &key)?,
74        })
75    }
76
77    pub fn sign(self, keychain: &Keychain) -> LbResult<SignedFile> {
78        pubkey::sign(&keychain.get_account()?.private_key, &keychain.get_pk()?, self, get_time)
79    }
80
81    pub fn sign_with(self, account: &Account) -> LbResult<SignedFile> {
82        pubkey::sign(&account.private_key, &account.public_key(), self, get_time)
83    }
84}
85
86// This is impl'd to avoid comparing encrypted values
87impl PartialEq for FileMetadata {
88    fn eq(&self, other: &Self) -> bool {
89        self.id == other.id
90            && self.file_type == other.file_type
91            && self.parent == other.parent
92            && self.name == other.name
93            && self.owner == other.owner
94            && self.is_deleted == other.is_deleted
95            && self.document_hmac == other.document_hmac
96            && self.user_access_keys == other.user_access_keys
97    }
98}
99
100impl fmt::Display for FileMetadata {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        write!(f, "{}", self.display())
103    }
104}
105
106#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Copy)]
107pub struct Owner(pub PublicKey);
108
109impl Hash for Owner {
110    fn hash<H: Hasher>(&self, state: &mut H) {
111        self.0.serialize().hash(state)
112    }
113}
114
115impl Debug for Owner {
116    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117        write!(f, "pub_key:{}", base64::encode(self.0.serialize_compressed()))
118    }
119}
120
121#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize, Copy)]
122pub enum FileType {
123    Document,
124    Folder,
125    Link { target: Uuid },
126}
127
128impl FromStr for FileType {
129    type Err = ();
130    fn from_str(input: &str) -> Result<FileType, Self::Err> {
131        match input {
132            "Document" => Ok(FileType::Document),
133            "Folder" => Ok(FileType::Folder),
134            _ => Err(()),
135        }
136    }
137}
138
139impl fmt::Display for FileType {
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        write!(f, "{self:?}")
142    }
143}
144
145#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
146pub struct FileDiff<F: FileLike> {
147    pub old: Option<F>,
148    pub new: F,
149}
150
151impl<F: FileLike> Debug for FileDiff<F> {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        let mut result = &mut f.debug_struct("FileDiff");
154        result = result.field("id", self.id());
155        for diff in self.diff() {
156            result = match diff {
157                Diff::New => result.field("is_new", &true),
158                Diff::Parent => result.field("new_parent", &self.new.parent()),
159                Diff::Name => result.field("new_name", &self.new.secret_name()),
160                Diff::Owner => result.field("new_owner", &self.new.owner()),
161                Diff::Deleted => result.field("new_deleted", &self.new.explicitly_deleted()),
162                Diff::Hmac => result.field("new_hmac", &self.new.document_hmac()),
163                Diff::UserKeys => result.field("new_user_keys", &true),
164            };
165        }
166        result.finish()
167    }
168}
169
170#[derive(PartialEq, Eq, Debug)]
171pub enum Diff {
172    New,
173    Parent,
174    Name,
175    Owner,
176    Deleted,
177    Hmac,
178    UserKeys,
179}
180
181impl<F: FileLike> FileDiff<F> {
182    pub fn id(&self) -> &Uuid {
183        self.new.id()
184    }
185
186    pub fn diff(&self) -> Vec<Diff> {
187        let new = &self.new;
188        use Diff::*;
189        match &self.old {
190            None => vec![New],
191            Some(old) => {
192                let mut changes = vec![];
193
194                if old.parent() != new.parent() {
195                    changes.push(Parent)
196                }
197
198                if old.secret_name() != new.secret_name() {
199                    changes.push(Name)
200                }
201
202                if old.owner() != new.owner() {
203                    changes.push(Owner)
204                }
205
206                if old.explicitly_deleted() != new.explicitly_deleted() {
207                    changes.push(Deleted)
208                }
209
210                if old.document_hmac() != new.document_hmac() {
211                    changes.push(Hmac);
212                }
213
214                if old.user_access_keys() != new.user_access_keys() {
215                    changes.push(UserKeys);
216                }
217
218                changes
219            }
220        }
221    }
222
223    pub fn new(new: F) -> Self {
224        let old = None;
225        Self { old, new }
226    }
227
228    pub fn edit(old: F, new: F) -> Self {
229        let old = Some(old);
230        Self { old, new }
231    }
232}