aboutsummaryrefslogtreecommitdiffstats
path: root/src/access/registry.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:09 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:09 +0100
commitf2487f5a1f897bb5499ffd782562f53967f20606 (patch)
tree3e6b64f4ef1ebe1c19e346c2080a5a7afb31ec26 /src/access/registry.rs
parentc82a62186a8e0a46fd41e08345a2fa3e54ddffe2 (diff)
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.
Diffstat (limited to 'src/access/registry.rs')
-rw-r--r--src/access/registry.rs26
1 files changed, 26 insertions, 0 deletions
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<User> {
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<String> {
+ 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()
@@ -301,6 +309,24 @@ mod tests {
}
#[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();
let entry = entry("x", 0, &[]);