aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake')
-rw-r--r--src/remake/quarantine.rs47
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 {