feat(geschichten): show blog writers' own drafts on the Geschichten overview (#807) #813

Merged
marcel merged 13 commits from feat/issue-807-drafts-overview into main 2026-06-12 19:46:05 +02:00
2 changed files with 8 additions and 2 deletions
Showing only changes of commit e3140c4f99 - Show all commits

View File

@@ -117,12 +117,17 @@ public class GeschichteService {
*
* <p>Returns a {@link GeschichteSummary} projection — never carries items, preventing
* LazyInitializationException on the non-transactional list path.
*
* <p>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<GeschichteSummary> list(GeschichteStatus status, List<UUID> 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.

View File

@@ -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());