1use crate::model::access_info::UserAccessMode;
2use crate::model::file_like::FileLike;
3use crate::model::file_metadata::{Diff, FileDiff, FileType, Owner};
4use crate::model::filename::MAX_ENCRYPTED_FILENAME_LENGTH;
5use crate::model::lazy::LazyTree;
6use crate::model::staged::StagedTreeLike;
7use crate::model::tree_like::TreeLike;
8use crate::model::ValidationFailure;
9use std::collections::{HashMap, HashSet};
10
11use super::errors::{LbErrKind, LbResult};
12
13pub fn file_name(name: &str) -> LbResult<()> {
14 if name.is_empty() {
15 Err(LbErrKind::FileNameEmpty)?;
16 }
17 if name.contains('/') {
18 Err(LbErrKind::FileNameContainsSlash)?;
19 }
20 Ok(())
21}
22
23pub fn not_root<F: FileLike>(file: &F) -> LbResult<()> {
24 if file.is_root() {
25 Err(LbErrKind::RootModificationInvalid)?
26 } else {
27 Ok(())
28 }
29}
30
31pub fn is_folder<F: FileLike>(file: &F) -> LbResult<()> {
32 if file.is_folder() {
33 Ok(())
34 } else {
35 Err(LbErrKind::Validation(ValidationFailure::NonFolderWithChildren(*file.id())))?
36 }
37}
38
39pub fn is_document<F: FileLike>(file: &F) -> LbResult<()> {
40 if file.is_document() {
41 Ok(())
42 } else {
43 Err(LbErrKind::FileNotDocument)?
44 }
45}
46
47pub fn path(path: &str) -> LbResult<()> {
48 if path.contains("//") || path.is_empty() {
49 Err(LbErrKind::PathContainsEmptyFileName)?;
50 }
51
52 Ok(())
53}
54
55impl<T, Base, Local> LazyTree<T>
56where
57 T: StagedTreeLike<Base = Base, Staged = Local>,
58 Base: TreeLike<F = T::F>,
59 Local: TreeLike<F = T::F>,
60{
61 pub fn validate(&mut self, owner: Owner) -> LbResult<()> {
62 self.assert_no_root_changes()?;
64 self.assert_no_changes_to_deleted_files()?;
65 self.assert_all_filenames_size_limit()?;
66 self.assert_all_files_decryptable(owner)?;
67 self.assert_only_folders_have_children()?;
68 self.assert_all_files_same_owner_as_parent()?;
69
70 self.assert_no_cycles()?;
72 self.assert_no_path_conflicts()?;
73 self.assert_no_shared_links()?;
74 self.assert_no_duplicate_links()?;
75 self.assert_no_broken_links()?;
76 self.assert_no_owned_links()?;
77
78 self.assert_changes_authorized(owner)?;
80
81 Ok(())
82 }
83
84 pub fn assert_all_files_decryptable(&mut self, owner: Owner) -> LbResult<()> {
86 for file in self.ids().into_iter().filter_map(|id| self.maybe_find(&id)) {
87 if self.maybe_find_parent(file).is_none()
88 && !file
89 .user_access_keys()
90 .iter()
91 .any(|k| k.encrypted_for == owner.0)
92 {
93 Err(LbErrKind::Validation(ValidationFailure::Orphan(*file.id())))?;
94 }
95 }
96 Ok(())
97 }
98
99 pub fn assert_all_filenames_size_limit(&self) -> LbResult<()> {
100 for file in self.all_files()? {
101 if file.secret_name().encrypted_value.value.len() > MAX_ENCRYPTED_FILENAME_LENGTH {
102 return Err(LbErrKind::Validation(ValidationFailure::FileNameTooLong(*file.id())))?;
103 }
104 }
105 Ok(())
106 }
107
108 pub fn assert_only_folders_have_children(&self) -> LbResult<()> {
109 for file in self.all_files()? {
110 if let Some(parent) = self.maybe_find(file.parent()) {
111 if !parent.is_folder() {
112 Err(LbErrKind::Validation(ValidationFailure::NonFolderWithChildren(
113 *parent.id(),
114 )))?;
115 }
116 }
117 }
118 Ok(())
119 }
120
121 pub fn assert_all_files_same_owner_as_parent(&mut self) -> LbResult<()> {
124 for id in self.ids() {
125 if self.calculate_deleted(&id)? {
126 continue;
127 }
128 let file = self.find(&id)?;
129 if let Some(parent) = self.maybe_find(file.parent()) {
130 if parent.owner() != file.owner() {
131 Err(LbErrKind::Validation(ValidationFailure::FileWithDifferentOwnerParent(
132 *file.id(),
133 )))?;
134 }
135 }
136 }
137 Ok(())
138 }
139
140 pub fn assert_no_cycles(&mut self) -> LbResult<()> {
142 let mut owners_with_found_roots = HashSet::new();
143 let mut no_cycles_in_ancestors = HashSet::new();
144 for id in self.ids() {
145 let mut ancestors = HashSet::new();
146 let mut current_file = self.find(&id)?;
147 loop {
148 if no_cycles_in_ancestors.contains(current_file.id()) {
149 break;
150 } else if current_file.is_root() {
151 if owners_with_found_roots.insert(current_file.owner()) {
152 ancestors.insert(*current_file.id());
153 break;
154 } else {
155 Err(LbErrKind::Validation(ValidationFailure::Cycle(HashSet::from([id]))))?;
156 }
157 } else if ancestors.contains(current_file.parent()) {
158 Err(LbErrKind::Validation(ValidationFailure::Cycle(
159 self.ancestors(current_file.id())?,
160 )))?;
161 }
162 ancestors.insert(*current_file.id());
163 current_file = match self.maybe_find_parent(current_file) {
164 Some(file) => file,
165 None => {
166 if !current_file.user_access_keys().is_empty() {
167 break;
168 } else {
169 return Err(LbErrKind::FileParentNonexistent)?;
170 }
171 }
172 }
173 }
174 no_cycles_in_ancestors.extend(ancestors);
175 }
176 Ok(())
177 }
178
179 pub fn assert_no_path_conflicts(&mut self) -> LbResult<()> {
180 let mut id_by_name = HashMap::new();
181 for id in self.ids() {
182 if !self.calculate_deleted(&id)? {
183 let file = self.find(&id)?;
184 if file.is_root() || self.maybe_find(file.parent()).is_none() {
185 continue;
186 }
187 if let Some(conflicting) = id_by_name.remove(file.secret_name()) {
188 Err(LbErrKind::Validation(ValidationFailure::PathConflict(HashSet::from([
189 conflicting,
190 *file.id(),
191 ]))))?;
192 }
193 id_by_name.insert(file.secret_name().clone(), *file.id());
194 }
195 }
196 Ok(())
197 }
198
199 pub fn assert_no_shared_links(&self) -> LbResult<()> {
200 for link in self.ids() {
201 let meta = self.find(&link)?;
202 if let FileType::Link { target: _ } = meta.file_type() {
203 if meta.is_shared() {
204 Err(LbErrKind::Validation(ValidationFailure::SharedLink {
205 link,
206 shared_ancestor: link,
207 }))?;
208 }
209 for ancestor in self.ancestors(&link)? {
210 if self.find(&ancestor)?.is_shared() {
211 Err(LbErrKind::Validation(ValidationFailure::SharedLink {
212 link,
213 shared_ancestor: ancestor,
214 }))?;
215 }
216 }
217 }
218 }
219 Ok(())
220 }
221
222 pub fn assert_no_duplicate_links(&mut self) -> LbResult<()> {
223 let mut linked_targets = HashSet::new();
224 for link in self.ids() {
225 if self.calculate_deleted(&link)? {
226 continue;
227 }
228 if let FileType::Link { target } = self.find(&link)?.file_type() {
229 if !linked_targets.insert(target) {
230 Err(LbErrKind::Validation(ValidationFailure::DuplicateLink { target }))?;
231 }
232 }
233 }
234 Ok(())
235 }
236
237 pub fn assert_no_broken_links(&mut self) -> LbResult<()> {
243 for link in self.ids() {
244 if let FileType::Link { target } = self.find(&link)?.file_type() {
245 if !self.calculate_deleted(&link)? && self.maybe_find(&target).is_none() {
246 Err(LbErrKind::Validation(ValidationFailure::BrokenLink(link)))?;
247 }
248 }
249 }
250 Ok(())
251 }
252
253 pub fn assert_no_owned_links(&self) -> LbResult<()> {
254 for link in self.ids() {
255 if let FileType::Link { target } = self.find(&link)?.file_type() {
256 if let Some(target_owner) = self.maybe_find(&target).map(|f| f.owner()) {
257 if self.find(&link)?.owner() == target_owner {
258 Err(LbErrKind::Validation(ValidationFailure::OwnedLink(link)))?;
259 }
260 }
261 }
262 }
263 Ok(())
264 }
265
266 pub fn assert_no_root_changes(&mut self) -> LbResult<()> {
267 for id in self.tree.staged().ids() {
268 if let Some(base) = self.tree.base().maybe_find(&id) {
270 if base.is_root() {
271 Err(LbErrKind::RootModificationInvalid)?;
272 }
273 }
274 if self.find(&id)?.is_root() {
276 Err(LbErrKind::Validation(ValidationFailure::Cycle(
277 vec![id].into_iter().collect(),
278 )))?;
279 }
280 }
281 Ok(())
282 }
283
284 pub fn assert_no_changes_to_deleted_files(&mut self) -> LbResult<()> {
285 for id in self.tree.staged().ids() {
286 let mut base = self.tree.base().to_lazy();
288 if base.maybe_find(&id).is_some() && base.calculate_deleted(&id)? {
289 Err(LbErrKind::Validation(ValidationFailure::DeletedFileUpdated(id)))?;
290 }
291 if self.calculate_deleted(&id)? {
293 if let Some(base) = self.tree.base().maybe_find(&id) {
294 if FileDiff::edit(&base, &self.find(&id)?)
295 .diff()
296 .iter()
297 .any(|d| d != &Diff::Deleted)
298 {
299 Err(LbErrKind::Validation(ValidationFailure::DeletedFileUpdated(id)))?;
300 }
301 }
302 }
303 }
304 Ok(())
305 }
306
307 pub fn assert_changes_authorized(&mut self, owner: Owner) -> LbResult<()> {
308 let new_files = {
327 let mut new_files = HashSet::new();
328 for id in self.tree.staged().ids() {
329 if self.tree.base().maybe_find(&id).is_none() {
330 new_files.insert(id);
331 }
332 }
333 new_files
334 };
335
336 for file_diff in self.diffs()? {
337 for field_diff in file_diff.diff() {
338 match field_diff {
339 Diff::New | Diff::Name | Diff::Deleted => {
340 let file =
342 if let Some(ref old) = file_diff.old { old } else { &file_diff.new };
343 if !new_files.contains(file.parent()) {
345 if let Some(parent) = self.maybe_find(file.parent()) {
347 if self.access_mode(owner, parent.id())?
348 < Some(UserAccessMode::Write)
349 {
350 Err(LbErrKind::InsufficientPermission)?;
352 }
353 } else {
354 Err(LbErrKind::InsufficientPermission)?;
356 }
357 }
358 }
359 Diff::Parent | Diff::Owner => {
360 {
362 let parent = if let Some(ref old) = file_diff.old {
363 old.parent()
364 } else {
365 return Err(LbErrKind::Unexpected(
366 "Non-New FileDiff with no old".to_string(),
367 )
368 .into());
369 };
370
371 if let Some(parent) = self.maybe_find(parent) {
373 if self.access_mode(owner, parent.id())?
374 < Some(UserAccessMode::Write)
375 {
376 Err(LbErrKind::InsufficientPermission)?;
378 }
379 } else {
380 Err(LbErrKind::InsufficientPermission)?;
382 }
383 }
384 {
386 let parent = file_diff.new.parent();
387
388 if !new_files.contains(parent) {
390 if let Some(parent) = self.maybe_find(parent) {
392 if self.access_mode(owner, parent.id())?
393 < Some(UserAccessMode::Write)
394 {
395 Err(LbErrKind::InsufficientPermission)?;
397 }
398 } else {
399 Err(LbErrKind::InsufficientPermission)?;
401 }
402 }
403 }
404 }
405 Diff::Hmac => {
406 if self.access_mode(owner, file_diff.id())? < Some(UserAccessMode::Write) {
408 Err(LbErrKind::InsufficientPermission)?;
409 }
410 }
411 Diff::UserKeys => {
412 let base_keys = {
414 if let Some(ref old) = file_diff.old {
415 let mut base_keys = HashMap::new();
416 for key in old.user_access_keys() {
417 base_keys.insert(
418 (Owner(key.encrypted_by), Owner(key.encrypted_for)),
419 (key.mode, key.deleted),
420 );
421 }
422 base_keys
423 } else {
424 return Err(LbErrKind::Unexpected(
425 "Non-New FileDiff with no old".to_string(),
426 ))?;
427 }
428 };
429 for key in file_diff.new.user_access_keys() {
430 if let Some((base_mode, base_deleted)) =
431 base_keys.get(&(Owner(key.encrypted_by), Owner(key.encrypted_for)))
432 {
433 let (staged_mode, staged_deleted) = (&key.mode, &key.deleted);
436 if *staged_deleted
438 && !*base_deleted
439 && self.access_mode(owner, file_diff.id())?
440 < Some(UserAccessMode::Write)
441 && owner.0 != key.encrypted_for
442 {
443 Err(LbErrKind::InsufficientPermission)?;
444 }
445 if staged_mode != base_mode
447 && self.access_mode(owner, file_diff.id())?
448 < Some(UserAccessMode::Write)
449 {
450 Err(LbErrKind::InsufficientPermission)?;
451 }
452 } else {
453 if self.access_mode(owner, file_diff.id())? < Some(key.mode) {
457 Err(LbErrKind::InsufficientPermission)?;
458 }
459 }
460 }
461 }
462 }
463 }
464 }
465
466 Ok(())
467 }
468
469 fn diffs(&self) -> LbResult<Vec<FileDiff<Base::F>>> {
470 let mut result = Vec::new();
471 for id in self.tree.staged().ids() {
472 let staged = self.tree.staged().find(&id)?;
473 if let Some(base) = self.tree.base().maybe_find(&id) {
474 result.push(FileDiff::edit(base, staged));
475 } else {
476 result.push(FileDiff::new(staged));
477 }
478 }
479 Ok(result)
480 }
481}