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 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) (limited to 'src/access/envelope.rs') 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"))); } } -- cgit v1.3.1