From 41657a4d749b1f1d44663ff6504f2e784f091537 Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:21 +0100 Subject: access: strict policy, signed paths "values": "signed" (strict, requiring signatures too): the registry, _continuum/access, bans and the paths in _continuum/access/userSigned (* for any key) hold values their users signed. Envelopes read as the values they hold where the node reads protocol data. Auth plan M7. --- src/access/envelope.rs | 58 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 14 deletions(-) (limited to 'src/access/envelope.rs') diff --git a/src/access/envelope.rs b/src/access/envelope.rs index a875dca..7244a3b 100644 --- a/src/access/envelope.rs +++ b/src/access/envelope.rs @@ -1,5 +1,6 @@ -//! Values signed by their user: at a path the swarm lists as user-signed -//! (`_continuum/access/userSigned`, `listed`), the value is a signed envelope, one +//! Values signed by their user: under a strict policy (`access::policy`), at what +//! decides who may do what and at the paths the swarm lists as user-signed +//! (`_continuum/access/userSigned`, `listed`; `signed_paths`), the value is a signed envelope, one //! string, `cv1..` (each base64url, unpadded). The payload is JSON: //! the `path` it is for (segments), the `value` (`null`: deleted), the `user` who //! signed it, when (`at`, Unix ms, by the signer's clock), and `prev`: the SHA-256 (hex) @@ -34,7 +35,7 @@ pub(crate) fn listed_at() -> Vec { /// The paths `doc` lists as user-signed; an entry that isn't a list of strings is left /// out. pub(crate) fn listed(doc: &impl ReadDoc) -> Vec> { - let Ok(Some(JsonValue::Array(entries))) = store::read_path(doc, &listed_at()) else { + let Ok(Some(JsonValue::Array(entries))) = store::read_plain(doc, &listed_at()) else { return Vec::new(); }; entries @@ -49,12 +50,35 @@ pub(crate) fn listed(doc: &impl ReadDoc) -> Vec> { .collect() } -/// The user-signed path `path` is at or within, if any (`listed`). -pub(crate) fn signed_path<'a>(listed: &'a [Vec], path: &[String]) -> Option<&'a [String]> { - listed +/// What decides who may do what, whose values are always user-signed under a strict +/// policy (`access::policy`): every user's entry, every entry of `_continuum/access`, +/// every ban. A `*` segment stands for any one key. +fn deciding() -> Vec> { + [meta::USERS, meta::ACCESS, meta::BANNED] .iter() - .find(|signed| path.starts_with(signed)) - .map(Vec::as_slice) + .map(|part| meta::path(&[part, "*"])) + .collect() +} + +/// The paths whose values their users sign under a strict policy: what decides who may +/// do what (`deciding`), and those `doc` lists (`listed`). +pub(crate) fn signed_paths(doc: &impl ReadDoc) -> Vec> { + let mut paths = deciding(); + paths.extend(listed(doc)); + paths +} + +/// The user-signed path `path` is at or within, if any, of `signed` (`signed_paths`): +/// its `*` segments as `path` has them. +pub(crate) fn signed_path(signed: &[Vec], path: &[String]) -> Option> { + signed.iter().find_map(|pattern| { + (path.len() >= pattern.len() + && pattern + .iter() + .zip(path) + .all(|(segment, key)| segment == "*" || segment == key)) + .then(|| path[..pattern.len()].to_vec()) + }) } /// A signed value's payload. @@ -242,13 +266,19 @@ mod tests { change(&mut doc, |doc| { store::write_path(doc, &listed_at(), &json!([["config"], ["a", "b"], "junk"])).unwrap(); }); - let listed = listed(&doc); - assert_eq!(listed, vec![path("config"), path("a/b")]); + assert_eq!(listed(&doc), vec![path("config"), path("a/b")]); + let signed = signed_paths(&doc); + assert_eq!(signed_path(&signed, &path("config")), Some(path("config"))); + assert_eq!(signed_path(&signed, &path("a/b/c")), Some(path("a/b"))); + assert_eq!(signed_path(&signed, &path("a")), None); + assert_eq!( + signed_path(&signed, &path("_continuum/users/alice/caps")), + Some(path("_continuum/users/alice")) + ); + assert_eq!(signed_path(&signed, &path("_continuum/users")), None); assert_eq!( - signed_path(&listed, &path("config")), - Some(&path("config")[..]) + signed_path(&signed, &path("_continuum/access/policy")), + Some(path("_continuum/access/policy")) ); - assert_eq!(signed_path(&listed, &path("a/b/c")), Some(&path("a/b")[..])); - assert_eq!(signed_path(&listed, &path("a")), None); } } -- cgit v1.3.1