From f2487f5a1f897bb5499ffd782562f53967f20606 Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:09 +0100 Subject: access: keeping the swarm manageable A write that would leave its own writer unable to manage users (deleting, disabling or narrowing its entry, removing the registry) is refused; --protocol-writers can set a registry right that nobody may manage any more. Auth plan M1. --- src/access/registry.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src/access') diff --git a/src/access/registry.rs b/src/access/registry.rs index 4b4304a..948e0de 100644 --- a/src/access/registry.rs +++ b/src/access/registry.rs @@ -108,6 +108,14 @@ pub(crate) fn user(doc: &impl ReadDoc, name: &str) -> Option { value.is_object().then(|| User::from_json(&value)) } +/// The users who may manage users: enabled, with `write` on `_continuum/users`. +pub(crate) fn managers(doc: &impl ReadDoc) -> Vec { + names(doc) + .into_iter() + .filter(|name| caps_of(doc, name).allows(Right::Write, &users_dir())) + .collect() +} + /// What user `name` may do: none if it isn't registered (or is disabled). pub(crate) fn caps_of(doc: &impl ReadDoc, name: &str) -> Caps { user(doc, name).map(|user| user.caps()).unwrap_or_default() @@ -300,6 +308,24 @@ mod tests { assert!(!user.allows(Right::Read, &path("_continuum"))); } + #[test] + fn who_may_manage_users() { + let mut doc = Document::new(); + for (name, admin) in [("alice", true), ("bob", false)] { + let entry = entry("x", 0, &first_caps(admin)); + change(&mut doc, |tx| { + store::write_path(tx, &user_path(name), &entry).unwrap() + }); + } + assert_eq!(managers(&doc), vec!["alice"]); + let mut disabled = user_path("alice"); + disabled.push("disabled".to_owned()); + change(&mut doc, |tx| { + store::write_path(tx, &disabled, &json!(true)).unwrap() + }); + assert!(managers(&doc).is_empty()); + } + #[test] fn concurrent_grants_both_hold() { let mut ours = Document::new(); -- cgit v1.3.1