OP-887 Add GET /patients/{code}/export for GDPR data portability - #595
OP-887 Add GET /patients/{code}/export for GDPR data portability#595giuseppesorge wants to merge 2 commits into
Conversation
New endpoint returning all patient-linked data as JSON or RFC-4180 CSV sections depending on the Accept header, guarded by the patient.export authority. Profile photo is stripped from all nested DTOs. Also pins the PatientVaccine vaccine property mapping in PatVacMapper, fixing a pre-existing MappingException on /patientvaccines.
Prefix a cell value with a single quote when it starts with =, +, -, @, tab or carriage return, so a spreadsheet application does not interpret an exported field as a formula. Applied in escape(), before the RFC 4180 quoting, so it covers every cell (keys and values, including rendered nested JSON). Adds a unit test.
|
Hardened the CSV export against formula injection (commit
The fix prefixes any such value with a single quote ( Context: flagged by the automated security review of the pushed commits (MEDIUM). This only affects the CSV variant of the OP-887 export; the JSON body and the OP-888 full-record view (JSON only) are unaffected. |
See OP-887. Depends on informatici/openhospital-core#1633 (same branch name, so this PR's CI cross-builds against that core branch).
GET /patients/{code}/exportreturns the full patient aggregate as JSON; withAccept: text/csvit returns a CSV rendering — one RFC-4180 section per entity group with# <group>marker lines, CRLF, and an attachmentContent-Disposition(patient_<code>_export.csv). Media types are evaluated in header order, JSON winning when both are acceptable (q-values are not weighted; documented in the javadoc). The endpoint requires the newpatient.exportauthority: the SecurityConfig rule is placed before the genericGET /patients/**matcher.@Operation(description = "GDPR Art. 20 - Right to data portability")per the ticket.openapi/oh.yamlwas regenerated and verified byte-identical (sorted-JSON compare) to the springdoc output of the running application.This PR also fixes a pre-existing production bug surfaced while wiring the mapper: the shared ModelMapper mis-resolved
PatientVaccine.vaccineontovaccineDate, soGET/POST/PUT /patientvaccinesfailed with a MappingException for any record with a non-null vaccine date. Pinned with an explicit typeMap inPatVacMapper; an audit of other implicit GenericMapper matches may be worth a follow-up ticket.Verified: controller and CSV tests green, full suite green (237 tests); live end-to-end on the demo database — 200 JSON with all entity groups populated (vaccines included, per the ticket's note), 200 CSV, 404 for an unknown patient, 403 for a user whose group lacks the permission.