aboutsummaryrefslogtreecommitdiffstats
path: root/src/access
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 09:33:41 +0000
committertv <tv@krebsco.de>2026-11-08 10:36:32 +0100
commit47c3c7842f6c31af18cfaa2d84ada5dc83b9831b (patch)
tree280530bae675ddb51fcc5a93e657f1016d6bdda1 /src/access
parentf713fe668c29ecd60df547d989b0ace40cfe4da0 (diff)
access: judged by the policy written under
The rules read the policy at the change's dependencies only for strict mode, so history from before the policy was judged by today's rules when a late node caught up, or when REFUSALS had it judged again. A change written while the document had no policy, or one this node doesn't know, is now judged by none. Review finding 6.
Diffstat (limited to 'src/access')
-rw-r--r--src/access/rules.rs37
-rw-r--r--src/access/screen.rs5
2 files changed, 40 insertions, 2 deletions
diff --git a/src/access/rules.rs b/src/access/rules.rs
index 96ee9d4..185996a 100644
--- a/src/access/rules.rs
+++ b/src/access/rules.rs
@@ -24,7 +24,8 @@
//! declared (`sealed_values`).
//!
//! In a swarm without a user registry (`access::registry`), whatever a user would need,
-//! any member may.
+//! any member may. A change written while the document had no policy (at its
+//! dependencies) is judged by none: the rules hold from the policy on.
use std::collections::BTreeSet;
@@ -92,6 +93,12 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result
};
}
let at = At::new(doc, change.seen).ok_or(Refusal::Unready)?;
+ // Written while the document had no policy (or one this node doesn't know): judged
+ // as it was then, by none. History from before the policy stays as it is.
+ let policy = super::policy::of(&at);
+ if !policy.enforced() {
+ return Ok(());
+ }
let refuse = |why: String| Err(Refusal::Rule(why));
let Some(node) = change.committer else {
return refuse("it names no node".to_owned());
@@ -161,7 +168,7 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result
return refuse(format!("{} may not write {}", who(change), shown(path)));
}
}
- if super::policy::of(&at).strict() {
+ if policy.strict() {
signed_values(&at, change.ops).or_else(refuse)?;
}
let declared = super::sealed::declared(&at);
@@ -332,6 +339,7 @@ mod tests {
fn swarm(users: bool) -> Document {
let mut doc = Document::new();
write(&mut doc, "_continuum/swarm", json!("s"));
+ write(&mut doc, "_continuum/access/policy", json!({"version": 1}));
write(&mut doc, "_continuum/members/a", json!({"since": 1}));
write(&mut doc, "_continuum/members/b", json!({"since": 1}));
write(
@@ -708,6 +716,31 @@ mod tests {
assert!(judged(vec![op("notes/x", Action::PutString("plain".to_owned()))]).is_ok());
}
+ /// Written before the policy: judged by none, whatever it does; from the policy on,
+ /// by the rules.
+ #[test]
+ fn before_the_policy() {
+ let mut doc = Document::new();
+ write(&mut doc, "_continuum/swarm", json!("s"));
+ write(&mut doc, "_continuum/members/a", json!({"since": 1}));
+ write(
+ &mut doc,
+ "_continuum/users/alice",
+ registry::entry("a", 1, &[]),
+ );
+ assert!(judged(&doc, "stranger", Some("mallory"), &["notes/x"]).is_ok());
+ write(&mut doc, "_continuum/access/policy", json!({"version": 1}));
+ assert!(refused(judged(
+ &doc,
+ "stranger",
+ Some("mallory"),
+ &["notes/x"]
+ )));
+ // A policy this node doesn't know: judged by none either.
+ write(&mut doc, "_continuum/access/policy", json!({"version": 9}));
+ assert!(judged(&doc, "stranger", Some("mallory"), &["notes/x"]).is_ok());
+ }
+
/// Roots: the first change of a swarm's first epoch, a later epoch's snapshot.
#[test]
fn roots() {
diff --git a/src/access/screen.rs b/src/access/screen.rs
index 8d23b47..35a2af7 100644
--- a/src/access/screen.rs
+++ b/src/access/screen.rs
@@ -579,6 +579,11 @@ mod tests {
};
transact(&mut theirs, UnixMillis(1), &writer, |doc| {
store::write_path(doc, &path("_continuum/swarm"), &json!("s"))?;
+ store::write_path(
+ doc,
+ &path("_continuum/access/policy"),
+ &json!({"version": 1}),
+ )?;
store::write_path(doc, &path("_continuum/users/alice"), &alice)
})
.unwrap();