From a2056647b6351797880741ea7d845657877587ed Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 8 Nov 2026 19:01:55 +0000 Subject: store: a write inside a list checked where the rules judge it Review 2, N9: the API checked a write's capability on the path asked (servers/list/3/name), the rules on the list it is in (servers/list): a user whose capability lay inside a list had every such write pass the API and refused by the node's own judgement (403, after rolling the document back). inspect::placed places a write's path in the document by the rule the inspector places operations by (Place::child: cut after the first list or text on the way), and the store's writes check capabilities there. One rule for both; a test checks placed against the paths the inspector finds in the change such a write makes. --- src/api/store.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src/api') diff --git a/src/api/store.rs b/src/api/store.rs index 16dd966..e43d129 100644 --- a/src/api/store.rs +++ b/src/api/store.rs @@ -472,7 +472,7 @@ pub(crate) async fn put_store_entry( let path = if own_entry { crate::access::registry::user_path(&user.0) } else { - parse_write_path(&state, &user, &access, &key, "PUT")? + parse_write_path(&state, &user, &access, &key, "PUT").await? }; let sign = Sign::Write { path: path.clone(), @@ -662,8 +662,9 @@ fn parse_key_path(key: &str) -> Result, StatusCode> { /// what every node acts on, so a write there acts on the whole swarm, and each is /// logged. Epoch metadata (and `_continuum` as a whole, which contains it) is never /// writable: the protocol reads it at the snapshot change, so an edit only misleads -/// whoever reads it later; nor is the swarm ID. -fn parse_write_path( +/// whoever reads it later; nor is the swarm ID. A path inside a list or text is +/// judged where the rules judge the write (`inspect::placed`): at the list. +async fn parse_write_path( state: &AppState, user: &AuthenticatedUser, access: &Access, @@ -671,12 +672,13 @@ fn parse_write_path( method: &str, ) -> Result, StatusCode> { let path = parse_key_path(key)?; + let placed = crate::document::inspect::placed(&state.doc.read().await.doc, &path); let protocol = meta::is_protocol(&path); // `_continuum` itself, epoch metadata and the swarm ID: nobody. let fixed = protocol && (path.len() < 2 || path[1] == meta::EPOCHS || path[1] == meta::SWARM); let allowed = match access { Access::Everything => !protocol || state.protocol_writers.contains(&user.0), - Access::Caps(caps) => caps.allows(crate::access::Right::Write, &path), + Access::Caps(caps) => caps.allows(crate::access::Right::Write, &placed), }; if !protocol && allowed { return Ok(path); @@ -936,7 +938,7 @@ pub(crate) async fn post_store_entry( if !state.is_ready().await { return Err(StatusCode::SERVICE_UNAVAILABLE.into()); } - let path = parse_write_path(&state, &user, &access, &key, "POST")?; + let path = parse_write_path(&state, &user, &access, &key, "POST").await?; let sign = Sign::Write { path: path.clone(), asked: query.signed, @@ -1050,7 +1052,7 @@ pub(crate) async fn delete_store_entry( Query(query): Query, RawQuery(raw_query): RawQuery, ) -> Result { - let path = parse_write_path(&state, &user, &access, &key, "DELETE")?; + let path = parse_write_path(&state, &user, &access, &key, "DELETE").await?; let sign = Sign::Write { path: path.clone(), asked: query.signed, -- cgit v1.3.1