diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/remake/quarantine.rs | 47 |
1 files changed, 35 insertions, 12 deletions
diff --git a/src/remake/quarantine.rs b/src/remake/quarantine.rs index e7214d6..24262fc 100644 --- a/src/remake/quarantine.rs +++ b/src/remake/quarantine.rs @@ -177,21 +177,29 @@ pub(crate) async fn replay<H: Host>( let replayed = { let entry = Arc::clone(&entry); let user = user.map(|axum::Extension(user)| user.0); - host.commit_doc_for(user, move |doc| { + host.commit_doc_for(user.clone(), move |doc| { + // Only where the operator may write, as every node judges the change by it + // (`access::rules`); the rest stays here. + let caps = user + .as_deref() + .map(|user| crate::access::registry::caps_of(&*doc, user)); + let registry = crate::access::registry::exists(&*doc); + let (paths, kept): (Vec<_>, Vec<_>) = entry.paths.iter().cloned().partition(|path| { + !registry + || caps + .as_ref() + .is_some_and(|caps| caps.allows(crate::access::Right::Write, path)) + }); // As they are: the operator decided they belong at their old paths. - Ok::<_, StoreError>(delta::replay_paths( - &entry.document, - doc, - &entry.paths, - &Translator::default(), - )) + let stats = delta::replay_paths(&entry.document, doc, &paths, &Translator::default()); + Ok::<_, StoreError>((stats, kept)) }) .await }; // The commit's task held the only other reference, and it is done. - let entry = Arc::into_inner(entry).expect("the replay's reference is gone"); - let stats = match replayed { - Ok(stats) => stats, + let mut entry = Arc::into_inner(entry).expect("the replay's reference is gone"); + let (stats, kept) = match replayed { + Ok(replayed) => replayed, Err(error) => { // Rejoining since the check above: keep the entry for later. host.remake().restore_quarantined(entry); @@ -199,6 +207,14 @@ pub(crate) async fn replay<H: Host>( } }; + if stats.replayed == 0 && !kept.is_empty() && kept.len() == entry.paths.len() { + host.remake().restore_quarantined(entry); + return error_response( + StatusCode::FORBIDDEN, + "the user may write none of its paths", + None, + ); + } if stats.failed > 0 && stats.replayed == 0 { // Nothing went in; keep the entry for another look. host.remake().restore_quarantined(entry); @@ -208,9 +224,16 @@ pub(crate) async fn replay<H: Host>( Some(json!({ "failed": stats.failed })), ); } + let left = kept.len(); + if !kept.is_empty() { + // Those the user may not write, for one who may. + entry.paths = kept; + host.remake().restore_quarantined(entry); + } - info!(quarantine = %id, replayed = stats.replayed, failed = stats.failed, "Replayed quarantined changes"); - Json(json!({ "replayed": stats.replayed, "failed": stats.failed })).into_response() + info!(quarantine = %id, replayed = stats.replayed, failed = stats.failed, left, "Replayed quarantined changes"); + Json(json!({ "replayed": stats.replayed, "failed": stats.failed, "left": left })) + .into_response() } pub(crate) async fn discard<H: Host>(State(host): State<H>, Path(id): Path<String>) -> Response { |
