diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java index 0b86bbde..919e3ee6 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java @@ -7,6 +7,8 @@ import java.util.UUID; import org.raddatz.familienarchiv.dto.PersonUpdateDTO; import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.Person; +import org.raddatz.familienarchiv.security.Permission; +import org.raddatz.familienarchiv.security.RequirePermission; import org.raddatz.familienarchiv.service.DocumentService; import org.raddatz.familienarchiv.service.PersonService; import org.springframework.http.HttpStatus; @@ -52,6 +54,7 @@ public class PersonController { } @PostMapping + @RequirePermission(Permission.WRITE_ALL) public ResponseEntity createPerson(@RequestBody Map body) { String firstName = body.get("firstName"); String lastName = body.get("lastName"); @@ -62,6 +65,7 @@ public class PersonController { } @PutMapping("/{id}") + @RequirePermission(Permission.WRITE_ALL) public ResponseEntity updatePerson(@PathVariable UUID id, @RequestBody PersonUpdateDTO dto) { if (dto.getFirstName() == null || dto.getFirstName().isBlank() || dto.getLastName() == null || dto.getLastName().isBlank()) { @@ -74,6 +78,7 @@ public class PersonController { @PostMapping("/{id}/merge") @ResponseStatus(HttpStatus.NO_CONTENT) + @RequirePermission(Permission.WRITE_ALL) public void mergePerson(@PathVariable UUID id, @RequestBody Map body) { String targetIdStr = body.get("targetPersonId"); if (targetIdStr == null || targetIdStr.isBlank()) { diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java index a56df834..b6444de1 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java @@ -162,7 +162,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void createPerson_returns400_whenFirstNameIsMissing() throws Exception { mockMvc.perform(post("/api/persons") .contentType(MediaType.APPLICATION_JSON) @@ -171,7 +171,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void createPerson_returns400_whenFirstNameIsBlank() throws Exception { mockMvc.perform(post("/api/persons") .contentType(MediaType.APPLICATION_JSON) @@ -180,7 +180,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void createPerson_returns400_whenLastNameIsMissing() throws Exception { mockMvc.perform(post("/api/persons") .contentType(MediaType.APPLICATION_JSON) @@ -189,7 +189,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void createPerson_returns400_whenLastNameIsBlank() throws Exception { mockMvc.perform(post("/api/persons") .contentType(MediaType.APPLICATION_JSON) @@ -198,7 +198,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void createPerson_returns200_whenValid() throws Exception { Person saved = Person.builder().id(UUID.randomUUID()).firstName("Hans").lastName("Müller").build(); when(personService.createPerson(eq("Hans"), eq("Müller"), any())).thenReturn(saved); @@ -221,7 +221,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void updatePerson_returns400_whenFirstNameIsBlank() throws Exception { mockMvc.perform(put("/api/persons/{id}", UUID.randomUUID()) .contentType(MediaType.APPLICATION_JSON) @@ -230,7 +230,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void updatePerson_returns400_whenLastNameIsNull() throws Exception { mockMvc.perform(put("/api/persons/{id}", UUID.randomUUID()) .contentType(MediaType.APPLICATION_JSON) @@ -239,7 +239,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void updatePerson_returns200_whenValid() throws Exception { UUID id = UUID.randomUUID(); Person updated = Person.builder().id(id).firstName("Hans").lastName("Müller").build(); @@ -263,7 +263,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void mergePerson_returns400_whenTargetPersonIdIsMissing() throws Exception { mockMvc.perform(post("/api/persons/{id}/merge", UUID.randomUUID()) .contentType(MediaType.APPLICATION_JSON) @@ -272,7 +272,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void mergePerson_returns400_whenTargetPersonIdIsBlank() throws Exception { mockMvc.perform(post("/api/persons/{id}/merge", UUID.randomUUID()) .contentType(MediaType.APPLICATION_JSON) @@ -281,7 +281,7 @@ class PersonControllerTest { } @Test - @WithMockUser + @WithMockUser(authorities = "WRITE_ALL") void mergePerson_returns204_whenValid() throws Exception { UUID sourceId = UUID.randomUUID(); UUID targetId = UUID.randomUUID(); @@ -304,4 +304,33 @@ class PersonControllerTest { .content("{\"firstName\":\"Hans\",\"lastName\":\" \"}")) .andExpect(status().isBadRequest()); } + + // ─── Phase 1.1: @RequirePermission(WRITE_ALL) on write endpoints ────────── + + @Test + @WithMockUser(authorities = "READ_ALL") + void createPerson_returns403_whenUserHasOnlyReadPermission() throws Exception { + mockMvc.perform(post("/api/persons") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Hans\",\"lastName\":\"Müller\"}")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "READ_ALL") + void updatePerson_returns403_whenUserHasOnlyReadPermission() throws Exception { + mockMvc.perform(put("/api/persons/{id}", UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Hans\",\"lastName\":\"Müller\"}")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "READ_ALL") + void mergePerson_returns403_whenUserHasOnlyReadPermission() throws Exception { + mockMvc.perform(post("/api/persons/{id}/merge", UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"targetPersonId\":\"" + UUID.randomUUID() + "\"}")) + .andExpect(status().isForbidden()); + } }