diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/access/rules.rs | 48 |
1 files changed, 46 insertions, 2 deletions
diff --git a/src/access/rules.rs b/src/access/rules.rs index 320ccd8..2746cb3 100644 --- a/src/access/rules.rs +++ b/src/access/rules.rs @@ -141,6 +141,15 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result && store::read_path(&at, path).ok().flatten().is_none() }) .collect(); + // Whether the change only makes a map at `path`, where there is none. + let made = |path: &Path| { + change + .ops + .iter() + .filter(|op| op.path == *path) + .all(|op| op.action == Action::MakeMap) + && store::read_path(&at, path).ok().flatten().is_none() + }; for path in change.paths { if on_the_way.contains(path) { continue; @@ -168,8 +177,11 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result && user_may(&forgetting)) } (Some(meta::VOTES), Some(owner)) => owner == node, - // What holds them, made by whichever member first writes in it. - (Some(meta::MEMBERS | meta::VOTES | meta::REMAKES), None) => true, + // What holds them: made by whichever member first writes in it, where there + // is none; replaced or removed by one who may write it. + (Some(meta::MEMBERS | meta::VOTES | meta::REMAKES), None) => { + made(path) || user_may(path) + } // Written by a root only. (Some(meta::EPOCHS | meta::SWARM), _) => false, // Logging out: a user revokes its own tokens (`POST /auth/logout`); one who @@ -197,6 +209,18 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result .any(|other| other == &meta::path(&[meta::REMAKES, id])) } }, + // The record, made where there is none, and its proposal, written where + // there is none: by whichever member proposes. Replaced or removed by + // one who may write them. + None => made(path) || user_may(path), + Some("proposal") => { + store::read_path(&at, &meta::path(&[meta::REMAKES, id, "proposal"])) + .ok() + .flatten() + .is_none() + || user_may(path) + } + // The rest (an abort asked for), any member's. _ => true, }, _ => user_may(path), @@ -500,6 +524,26 @@ mod tests { &["_continuum/remakes/r1/decision"] ))); assert!(judged(&doc, "b", None, &["_continuum/remakes/r1/abortRequested"]).is_ok()); + // Its proposal: written where there is none; another's replaced, by one who may. + assert!(judged(&doc, "b", None, &["_continuum/remakes/r3/proposal"]).is_ok()); + assert!(refused(judged( + &doc, + "b", + None, + &["_continuum/remakes/r1/proposal"] + ))); + assert!(refused(judged( + &doc, + "b", + None, + &["_continuum/remakes/r1/proposal/coordinator"] + ))); + // What holds the records, a member's entries, votes: made where there are none, + // never replaced or removed by a member. + assert!(refused(judged(&doc, "b", None, &["_continuum/remakes"]))); + assert!(refused(judged(&doc, "b", None, &["_continuum/members"]))); + assert!(refused(judged(&doc, "b", None, &["_continuum/remakes/r1"]))); + assert!(judged(&doc, "b", None, &["_continuum/votes"]).is_ok()); // A record carried into a new epoch whole: its proposal with its decision. assert!( judged( |
