diff options
Diffstat (limited to 'src/access/rules.rs')
| -rw-r--r-- | src/access/rules.rs | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/src/access/rules.rs b/src/access/rules.rs index 185996a..78557b0 100644 --- a/src/access/rules.rs +++ b/src/access/rules.rs @@ -18,6 +18,8 @@ //! user too (logging out); a member's forget record by that member too (cancelling it, //! alive); the rest by a user who may write there. //! * User data: by a user who may write there. +//! * A map made on the way to what a change writes, where there was none: as what is +//! written within it. //! * Under a strict policy (`access::policy`), the values their users sign //! (`access::envelope`) only as they signed them (`signed_values`). //! * Where the swarm declares values sealed (`access::sealed`), only those sealed as @@ -123,7 +125,26 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result (true, Some(user)) => registry::caps_of(&at, user).allows(Right::Write, path), (true, None) => false, }; + // Maps made on the way to what the change writes, where there were none + // (`store::write_path` makes them): judged by what is written within them. + let on_the_way: BTreeSet<&Path> = change + .paths + .iter() + .filter(|path| { + let mut here = change.ops.iter().filter(|op| op.path == **path).peekable(); + here.peek().is_some() + && here.all(|op| op.action == Action::MakeMap) + && change + .paths + .iter() + .any(|other| other.len() > path.len() && other.starts_with(path)) + && store::read_path(&at, path).ok().flatten().is_none() + }) + .collect(); for path in change.paths { + if on_the_way.contains(path) { + continue; + } if !meta::is_protocol(path) { if !user_may(path) { return refuse(format!("{} may not write {}", who(change), shown(path))); @@ -741,6 +762,69 @@ mod tests { assert!(judged(&doc, "stranger", Some("mallory"), &["notes/x"]).is_ok()); } + /// A map made on the way to what is written, where there was none: judged by what is + /// written within it; one made where there is one, or holding nothing written, as + /// any write there. + #[test] + fn maps_made_on_the_way() { + let mut doc = swarm(true); + write( + &mut doc, + "_continuum/users/bob", + registry::entry("a", 2, &[(path("servers/eu"), &[Right::Write][..])]), + ); + let judged_ops = |doc: &Document, ops: Vec<Op>| { + let deps = doc.get_heads(); + let touched: BTreeSet<Path> = ops.iter().map(|op| op.path.clone()).collect(); + judge( + doc, + Epoch::GENESIS, + &Change { + hash: ChangeHash([9; 32]), + deps: &deps, + seen: &deps, + committer: Some("a"), + user: Some("bob"), + paths: &touched, + ops: &ops, + }, + ) + }; + let op = |at: &str, action: Action| Op { + path: path(at), + action, + }; + let first = vec![ + op("servers", Action::MakeMap), + op("servers/eu", Action::MakeMap), + op("servers/eu/0", Action::Other), + ]; + assert!(judged_ops(&doc, first).is_ok()); + // Nothing written within: a map bob may not make. + assert!(refused(judged_ops( + &doc, + vec![op("servers", Action::MakeMap)] + ))); + // Within it, where bob may not write. + assert!(refused(judged_ops( + &doc, + vec![ + op("servers", Action::MakeMap), + op("servers/us", Action::Other) + ] + ))); + // Where there is one already: replacing it. + write(&mut doc, "servers", json!({"us": 1})); + assert!(refused(judged_ops( + &doc, + vec![ + op("servers", Action::MakeMap), + op("servers/eu", Action::MakeMap), + op("servers/eu/0", Action::Other) + ] + ))); + } + /// Roots: the first change of a swarm's first epoch, a later epoch's snapshot. #[test] fn roots() { |
