diff --git a/backend/src/main/java/org/raddatz/familienarchiv/geschichte/GeschichteService.java b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/GeschichteService.java index cbc8f6d1..ffc932e7 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/geschichte/GeschichteService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/geschichte/GeschichteService.java @@ -117,12 +117,17 @@ public class GeschichteService { * *

Returns a {@link GeschichteSummary} projection — never carries items, preventing * LazyInitializationException on the non-transactional list path. + * + *

Security: {@code null} status always resolves to PUBLISHED — even for blog writers. + * Only an explicit {@code DRAFT} request scopes the query to the caller's own drafts. + * This prevents CWE-639: a blog writer passing {@code null} must not see all authors' drafts. */ public List list(GeschichteStatus status, List personIds, UUID documentId, int limit) { - GeschichteStatus effective = currentUserHasBlogWrite() ? status : GeschichteStatus.PUBLISHED; + boolean isDraftRequest = currentUserHasBlogWrite() && status == GeschichteStatus.DRAFT; + GeschichteStatus effective = isDraftRequest ? GeschichteStatus.DRAFT : GeschichteStatus.PUBLISHED; int safeLimit = limit <= 0 ? DEFAULT_LIMIT : Math.min(limit, MAX_LIMIT); - UUID authorId = effective == GeschichteStatus.DRAFT ? currentUser().getId() : null; + UUID authorId = isDraftRequest ? currentUser().getId() : null; // When personIds is empty, personCount=0 short-circuits the IN() predicate. // Pass a sentinel UUID to avoid invalid empty IN() SQL while the predicate is skipped. diff --git a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/GeschichteServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/GeschichteServiceTest.java index 261b5bd9..1d183a3c 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/geschichte/GeschichteServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/geschichte/GeschichteServiceTest.java @@ -324,6 +324,7 @@ class GeschichteServiceTest { @DisplayName("security: DRAFT status scopes to current user only") void list_with_DRAFT_status_scopes_to_current_user_not_all_authors() { authenticateAs(writer, Permission.BLOG_WRITE); + when(userService.findByEmail(writer.getEmail())).thenReturn(writer); when(geschichteRepository.findSummaries(any(), any(), any(), anyLong(), any())) .thenReturn(List.of());