From 3d36c262261db66e11ce8fa236abbd97191c96c1 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 14 May 2026 12:58:02 +0200 Subject: [PATCH] fix(import): exclude message field from API response; add auth boundary tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @JsonIgnore on ImportStatus.message — stops internal directory paths and raw exception text leaking through the admin import-status endpoint (CWE-209) - Add importStatus_messageField_notPresentInApiResponse test (red/green verified) - Add importStatus_returns401/403 auth boundary tests — documents and guards the @RequirePermission(ADMIN) protection against configuration drift Co-Authored-By: Claude Sonnet 4.6 --- .../importing/MassImportService.java | 3 ++- .../user/AdminControllerTest.java | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java b/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java index c7a575fc..ea648870 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java @@ -1,5 +1,6 @@ package org.raddatz.familienarchiv.importing; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.*; @@ -52,7 +53,7 @@ public class MassImportService { public enum State { IDLE, RUNNING, DONE, FAILED } - public record ImportStatus(State state, String statusCode, String message, int processed, LocalDateTime startedAt) {} + public record ImportStatus(State state, String statusCode, @JsonIgnore String message, int processed, LocalDateTime startedAt) {} private volatile ImportStatus currentStatus = new ImportStatus(State.IDLE, "IMPORT_IDLE", "Kein Import gestartet.", 0, null); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/user/AdminControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/user/AdminControllerTest.java index b8437abc..533aa7b3 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/user/AdminControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/user/AdminControllerTest.java @@ -56,6 +56,31 @@ class AdminControllerTest { .andExpect(jsonPath("$.processed").value(0)); } + @Test + @WithMockUser(authorities = "ADMIN") + void importStatus_messageField_notPresentInApiResponse() throws Exception { + MassImportService.ImportStatus status = new MassImportService.ImportStatus( + MassImportService.State.IDLE, "IMPORT_IDLE", "Kein Import gestartet.", 0, null); + when(massImportService.getStatus()).thenReturn(status); + + mockMvc.perform(get("/api/admin/import-status")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").doesNotExist()); + } + + @Test + void importStatus_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(get("/api/admin/import-status")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(authorities = "READ_ALL") + void importStatus_returns403_whenUserLacksAdminPermission() throws Exception { + mockMvc.perform(get("/api/admin/import-status")) + .andExpect(status().isForbidden()); + } + @Test void backfillVersions_returns401_whenUnauthenticated() throws Exception { mockMvc.perform(post("/api/admin/backfill-versions"))