fix(backend): reject foreign documentIds on submit and draft-sync (WP-68 F1)
submit and draft-sync took document ids straight from the request body with no ownership check: a caller who knew a foreign document's id could attach another citizen's upload to their own aanvraag (surfacing on the behandelaar's beoordeling screen, POSTed to OpenZaak as their zaakinformatieobject) and permanently block the victim's own delete by flipping Linked=true. ADR-0001 holds the FE has no authority; this trusted it anyway. Adds DocumentStore.ForeignIds(ids, owner) and calls it from both write paths before any write, 400 ProblemDetails on a mismatch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -314,9 +314,19 @@ api.MapPost("/applications", (CreateApplicationRequest req, HttpContext ctx) =>
|
||||
|
||||
// Draft sync per step — idempotent; keep it debounced on the client (it is chatty).
|
||||
api.MapPut("/applications/{id}", (string id, DraftSyncRequest req, HttpContext ctx) =>
|
||||
ApplicationStore.SyncDraft(id, ctx.Zorgverlener().Bsn, req.Draft, req.StepIndex, req.StepCount, req.DocumentIds)
|
||||
? Results.NoContent() : Results.NotFound())
|
||||
{
|
||||
var owner = ctx.Zorgverlener().Bsn;
|
||||
// A citizen may only reference their own uploads in a draft — reject before the sync
|
||||
// writes a foreign document id into the aanvraag (ADR-0001: the FE holds no authority).
|
||||
if (req.DocumentIds is { } ids && DocumentStore.ForeignIds(ids, owner) is { Count: > 0 } foreign)
|
||||
return Results.Problem(
|
||||
detail: $"Onbekend of niet-eigen document(en): {string.Join(", ", foreign)}.",
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
return ApplicationStore.SyncDraft(id, owner, req.Draft, req.StepIndex, req.StepCount, req.DocumentIds)
|
||||
? Results.NoContent() : Results.NotFound();
|
||||
})
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
// Cancel a Concept (cascades to its unlinked documents). Submitted aanvragen cannot
|
||||
@@ -353,6 +363,13 @@ api.MapPost("/applications/{id}/submit", (string id, SubmitApplicationRequest re
|
||||
var docs = req.Documents;
|
||||
var documentIds = docs?.Where(d => d.Channel == "digital" && d.DocumentId is not null).Select(d => d.DocumentId!).ToList();
|
||||
|
||||
// A citizen may only submit their own uploads — reject before the submit writes a
|
||||
// foreign document id onto the aanvraag (ADR-0001: the FE holds no authority).
|
||||
if (documentIds is { Count: > 0 } && DocumentStore.ForeignIds(documentIds, ctx.Zorgverlener().Bsn) is { Count: > 0 } foreignIds)
|
||||
return Results.Problem(
|
||||
detail: $"Onbekend of niet-eigen document(en): {string.Join(", ", foreignIds)}.",
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
|
||||
var submitted = ApplicationStore.Submit(id, ctx.Zorgverlener().Bsn, reject, autoApprovable, documentIds);
|
||||
if (submitted is null) return Results.Conflict();
|
||||
|
||||
@@ -401,6 +418,7 @@ api.MapPost("/applications/{id}/submit", (string id, SubmitApplicationRequest re
|
||||
return Results.Ok(new SubmitApplicationResponse(referentie, status));
|
||||
})
|
||||
.Produces<SubmitApplicationResponse>()
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.ProducesProblem(StatusCodes.Status409Conflict)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user