aboutsummaryrefslogtreecommitdiffstats
path: root/src/access/policy.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:21 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:21 +0100
commitf44fbf5b93269bb3573e8a2701e70ab0d7613153 (patch)
tree32915998f6e797afcc13f485ccde66bac0e94150 /src/access/policy.rs
parent389b11b597b6bf60f120b190002bbb76fff28dbf (diff)
access: the policy enforced
_continuum/access/policy ({"version": 1}) has every node screen every change it receives, on every path changes come in. A policy a node doesn't know stops its writes through the API. GET /admin/refused. Auth plan M6.
Diffstat (limited to 'src/access/policy.rs')
-rw-r--r--src/access/policy.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/access/policy.rs b/src/access/policy.rs
new file mode 100644
index 0000000..7ddef0e
--- /dev/null
+++ b/src/access/policy.rs
@@ -0,0 +1,93 @@
+//! The swarm's write protection policy (`_continuum/access/policy`): whether every node
+//! judges received changes by rules by path before applying them (`access::screen`,
+//! `access::rules`). Absent, nobody does: what exists keeps working as it did. Written
+//! by a user who may write protocol data, as any of it.
+//!
+//! `{"version": 1, "signatures": "optional"}`: rules enforced; a change is its claimed
+//! committer's whether or not a signature proves it (`document::signatures`). A policy
+//! this node doesn't know (another version, or a setting it lacks) stops it from taking
+//! writes through the API, and it receives without judging: it can't judge as the
+//! others do, so it writes nothing they might refuse, until it is upgraded.
+
+use serde_json::Value as JsonValue;
+
+use crate::document::{ReadDoc, meta, store};
+
+/// The policy versions this node knows.
+pub(crate) const VERSION: u64 = 1;
+
+/// Where the policy is.
+pub(crate) fn path() -> Vec<String> {
+ meta::path(&[meta::ACCESS, "policy"])
+}
+
+/// What the policy says.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) enum Policy {
+ /// None: changes aren't judged.
+ Open,
+ /// Rules by path enforced.
+ Enforced,
+ /// One this node doesn't know, and why.
+ Unknown(String),
+}
+
+impl Policy {
+ pub(crate) fn enforced(&self) -> bool {
+ matches!(self, Policy::Enforced)
+ }
+}
+
+/// The policy `doc` holds.
+pub(crate) fn of(doc: &impl ReadDoc) -> Policy {
+ let value = match store::read_path(doc, &path()) {
+ Ok(Some(value)) => value,
+ Ok(None) => return Policy::Open,
+ Err(error) => return Policy::Unknown(format!("it doesn't read: {error}")),
+ };
+ let version = value.get("version").and_then(JsonValue::as_u64);
+ if version != Some(VERSION) {
+ return Policy::Unknown(format!(
+ "version {}, this node knows {VERSION}",
+ value
+ .get("version")
+ .map_or("none".to_owned(), JsonValue::to_string)
+ ));
+ }
+ match value.get("signatures").and_then(JsonValue::as_str) {
+ None | Some("optional") => Policy::Enforced,
+ Some(other) => Policy::Unknown(format!("signatures {other:?}")),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::document::Document;
+ use crate::document::testing::change;
+ use serde_json::json;
+
+ fn with(policy: JsonValue) -> Policy {
+ let mut doc = Document::new();
+ change(&mut doc, |doc| {
+ store::write_path(doc, &path(), &policy).unwrap();
+ });
+ of(&doc)
+ }
+
+ #[test]
+ fn what_the_policy_says() {
+ assert_eq!(of(&Document::new()), Policy::Open);
+ assert_eq!(with(json!({"version": 1})), Policy::Enforced);
+ assert_eq!(
+ with(json!({"version": 1, "signatures": "optional"})),
+ Policy::Enforced
+ );
+ assert!(matches!(with(json!({"version": 2})), Policy::Unknown(_)));
+ assert!(matches!(
+ with(json!({"version": 1, "signatures": "sometimes"})),
+ Policy::Unknown(_)
+ ));
+ assert!(matches!(with(json!("on")), Policy::Unknown(_)));
+ }
+}