From 7fed3e4478ad28151fab288d569a1268c1f3e4cf Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:21 +0100 Subject: api: signed values at the store PUT with application/continuum-signed writes an envelope where values are signed (403/409 saying why); reads open envelopes and name the signer (x-continuum-signed-by), ?raw=true shows them. Under strict, unsigned writes reaching a signed value and the users API are 409. Auth plan M7. --- src/access/envelope.rs | 45 +++++++++++++++++++++++++++++++++++++++------ src/access/mod.rs | 5 ----- src/access/policy.rs | 1 - 3 files changed, 39 insertions(+), 12 deletions(-) (limited to 'src/access') diff --git a/src/access/envelope.rs b/src/access/envelope.rs index 7244a3b..1804b98 100644 --- a/src/access/envelope.rs +++ b/src/access/envelope.rs @@ -72,15 +72,39 @@ pub(crate) fn signed_paths(doc: &impl ReadDoc) -> Vec> { /// 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()) + (path.len() >= pattern.len() && agree(pattern, path)) + .then(|| path[..pattern.len()].to_vec()) }) } +/// Whether `path` is where a user-signed value goes, of `signed` (`signed_paths`). +pub(crate) fn is_signed(signed: &[Vec], path: &[String]) -> bool { + signed + .iter() + .any(|pattern| pattern.len() == path.len() && agree(pattern, path)) +} + +/// Whether a user-signed value of `signed` may be below `path` (`signed_paths`). +pub(crate) fn above_signed(signed: &[Vec], path: &[String]) -> bool { + signed + .iter() + .any(|pattern| pattern.len() > path.len() && agree(pattern, path)) +} + +/// Whether a write at `path` reaches a user-signed value of `signed` (`signed_paths`): +/// at one, within one, or above one. +pub(crate) fn reaches_signed(signed: &[Vec], path: &[String]) -> bool { + signed.iter().any(|pattern| agree(pattern, path)) +} + +/// Whether `pattern` and `path` agree as far as both go (`*`: any key). +fn agree(pattern: &[String], path: &[String]) -> bool { + pattern + .iter() + .zip(path) + .all(|(segment, key)| segment == "*" || segment == key) +} + /// A signed value's payload. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub(crate) struct Payload { @@ -280,5 +304,14 @@ mod tests { signed_path(&signed, &path("_continuum/access/policy")), Some(path("_continuum/access/policy")) ); + assert!(is_signed(&signed, &path("_continuum/users/alice"))); + assert!(!is_signed(&signed, &path("_continuum/users/alice/caps"))); + assert!(above_signed(&signed, &path("_continuum/users"))); + assert!(above_signed(&signed, &path("a"))); + assert!(!above_signed(&signed, &path("a/b"))); + assert!(reaches_signed(&signed, &path("a"))); + assert!(reaches_signed(&signed, &path("a/b/c"))); + assert!(!reaches_signed(&signed, &path("a/c"))); + assert!(!reaches_signed(&signed, &path("_continuum/members/x"))); } } diff --git a/src/access/mod.rs b/src/access/mod.rs index b58355e..bdb5124 100644 --- a/src/access/mod.rs +++ b/src/access/mod.rs @@ -14,16 +14,11 @@ use serde::{Deserialize, Serialize}; use crate::document::meta; pub(crate) mod bans; -// Values signed by their users: what the store and the rules use; until then, the tests. -#[cfg_attr(not(test), allow(dead_code))] pub(crate) mod envelope; pub(crate) mod policy; pub(crate) mod registry; pub(crate) mod rules; pub(crate) mod screen; -// Users' keys and signatures: what signed values and logins by key use; until then, -// the tests. -#[cfg_attr(not(test), allow(dead_code))] pub(crate) mod sshsig; pub(crate) mod token; diff --git a/src/access/policy.rs b/src/access/policy.rs index 8efedff..85b562a 100644 --- a/src/access/policy.rs +++ b/src/access/policy.rs @@ -51,7 +51,6 @@ impl Policy { /// Whether values at the user-signed paths need their users' signatures /// (`values: "signed"`, `access::envelope::signed_paths`). - #[cfg_attr(not(test), allow(dead_code))] pub(crate) fn strict(&self) -> bool { matches!(self, Policy::Enforced { strict: true, .. }) } -- cgit v1.3.1