aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/access/rules.rs50
1 files changed, 46 insertions, 4 deletions
diff --git a/src/access/rules.rs b/src/access/rules.rs
index 78557b0..0de901a 100644
--- a/src/access/rules.rs
+++ b/src/access/rules.rs
@@ -154,15 +154,31 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result
let part = path.get(1).map(String::as_str);
let owner = path.get(2).map(String::as_str);
let allowed = match (part, owner) {
- // A member's entry, its votes: its own. Removing an entry, also a user's.
- (Some(meta::MEMBERS), Some(owner)) => owner == node || user_may(path),
+ // A member's entry, its votes: its own. Removing an entry, also a user's: one
+ // who may write it, or forgets the member in the same change
+ // (`DELETE /peers/{pk}`, which needs `_continuum/forgotten` alone).
+ (Some(meta::MEMBERS), Some(owner)) => {
+ let forgetting = meta::path(&[meta::FORGOTTEN, owner]);
+ owner == node
+ || user_may(path)
+ || (change
+ .paths
+ .iter()
+ .any(|other| other.starts_with(&forgetting))
+ && 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,
// Written by a root only.
(Some(meta::EPOCHS | meta::SWARM), _) => false,
- // Logging out: a user revokes its own tokens (`POST /auth/logout`).
- (Some(meta::LOGOUTS), Some(owner)) => change.user == Some(owner) || user_may(path),
+ // Logging out: a user revokes its own tokens (`POST /auth/logout`); one who
+ // manages users, anyone's (`DELETE /admin/users/{name}/tokens`).
+ (Some(meta::LOGOUTS), Some(owner)) => {
+ change.user == Some(owner)
+ || user_may(path)
+ || user_may(&meta::path(&[meta::USERS]))
+ }
// A node forgotten while alive cancels its own record (`swarm::peer::forget`).
(Some(meta::FORGOTTEN), Some(owner)) => owner == node || user_may(path),
(Some(meta::REMAKES), Some(id)) => match path.get(3).map(String::as_str) {
@@ -463,6 +479,32 @@ mod tests {
let tokens = "_continuum/logouts/bob";
assert!(judged(&doc, "a", Some("bob"), &[tokens]).is_ok());
assert!(refused(judged(&doc, "a", Some("alice"), &[tokens])));
+ // Revoked by one who manages users.
+ let mut managed = swarm(true);
+ write(
+ &mut managed,
+ "_continuum/users/carol",
+ registry::entry("a", 2, &[(path("_continuum/users"), &[Right::Write][..])]),
+ );
+ assert!(judged(&managed, "a", Some("carol"), &[tokens]).is_ok());
+ // A member forgotten, its entry removed with it, by one who may forget only.
+ write(
+ &mut managed,
+ "_continuum/users/dave",
+ registry::entry(
+ "a",
+ 3,
+ &[(path("_continuum/forgotten"), &[Right::Write][..])],
+ ),
+ );
+ let forgetting = ["_continuum/forgotten/b", "_continuum/members/b"];
+ assert!(judged(&managed, "a", Some("dave"), &forgetting).is_ok());
+ assert!(refused(judged(
+ &managed,
+ "a",
+ Some("dave"),
+ &["_continuum/members/b"]
+ )));
// A forget record: cancelled by the node it names, not by another.
let forgotten = "_continuum/forgotten/a";
assert!(judged(&doc, "a", None, &[forgotten]).is_ok());