aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/access.md9
-rw-r--r--src/access/rules.rs48
2 files changed, 53 insertions, 4 deletions
diff --git a/docs/access.md b/docs/access.md
index f7546aa..eb9efc9 100644
--- a/docs/access.md
+++ b/docs/access.md
@@ -224,8 +224,13 @@ stays as it is, wherever it comes from later (a joining node catching up, say).
member in the same change (`DELETE /peers/{pk}`, with `_continuum/forgotten`).
* Epochs and the swarm ID (`_continuum/epochs`, `_continuum/swarm`) by an epoch's first
change only (the genesis change, a remake's snapshot).
-* A remake's decision by the coordinator its proposal names; the rest of a record by
- any member.
+* A remake's record and its proposal by whichever member proposes, where there are
+ none; its decision by the coordinator its proposal names; an abort asked for by any
+ member. Replacing or removing a record or its proposal, by a user who may write
+ there.
+* What holds the members' entries, votes and remake records (`_continuum/members`,
+ `_continuum/votes`, `_continuum/remakes`) made by whichever member first writes in
+ it, where there is none; replaced or removed by a user who may write there only.
* A user's tokens revoked by that user too (logging out), and by one who manages users.
* A member's forget record cancelled by that member too (forgotten while alive).
* Everything else, protocol data or not, by a user the registry lets write there.
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(