From c2a6a72fae190a133bd8ac7ee80ca71b5c45fb5d Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Sat, 11 Apr 2026 16:25:11 +0200 Subject: [PATCH 1/7] WIP: ticket service + controller before merge --- .../alfs/controllers/TicketController.java | 48 +++- .../example/alfs/services/TicketService.java | 207 ++++++++++++++++-- 2 files changed, 223 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/example/alfs/controllers/TicketController.java b/src/main/java/org/example/alfs/controllers/TicketController.java index 23ade1d..1cfcd24 100644 --- a/src/main/java/org/example/alfs/controllers/TicketController.java +++ b/src/main/java/org/example/alfs/controllers/TicketController.java @@ -1,9 +1,11 @@ package org.example.alfs.controllers; import jakarta.validation.Valid; +import org.example.alfs.dto.ticket.TicketAssignDTO; import org.example.alfs.dto.ticket.TicketCreateDTO; +import org.example.alfs.dto.ticket.TicketStatusUpdateDTO; import org.example.alfs.dto.ticket.TicketViewDTO; -import org.example.alfs.entities.Ticket; +import org.springframework.security.access.prepost.PreAuthorize; import org.example.alfs.services.TicketService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -16,7 +18,7 @@ @RequestMapping public class TicketController { - TicketService ticketService; + private final TicketService ticketService; public TicketController(TicketService ticketService) { @@ -25,7 +27,7 @@ public TicketController(TicketService ticketService) { } //create ticket - + @PreAuthorize("hasRole('REPORTER')") // should change later for anonymous access @GetMapping("/create") public String createNewTicketForm(Model model) { model.addAttribute("ticket", new TicketCreateDTO()); @@ -33,6 +35,7 @@ public String createNewTicketForm(Model model) { } //TODO REDIRECT, WHERE?? + @PreAuthorize("hasRole('REPORTER')") // should change later for anonymous access @PostMapping("/create") public String createNewTicket(@ModelAttribute("ticket") @Valid TicketCreateDTO ticketCreateDTO, BindingResult bindingResult) { if (bindingResult.hasErrors()) { @@ -46,7 +49,7 @@ public String createNewTicket(@ModelAttribute("ticket") @Valid TicketCreateDTO t } //view ticket by token - + //TODO, FIX SO ANONYMOUS USERS CAN USE @GetMapping("/view/token/{token}") public String viewTicketByToken(@PathVariable String token, Model model) { @@ -57,6 +60,7 @@ public String viewTicketByToken(@PathVariable String token, Model model) { } //view ticket by id + @PreAuthorize("hasAnyRole('ADMIN','INVESTIGATOR','REPORTER')") @GetMapping("/view/id/{id}") public String viewTicketById(@PathVariable Long id, Model model) { @@ -66,18 +70,42 @@ public String viewTicketById(@PathVariable Long id, Model model) { return "view"; } + //assign ticket - // TODO implement DTO + service + @PreAuthorize("hasRole('ADMIN')") @PostMapping("/{id}/assign") - public String assignTicket(@PathVariable Long id) { - return "redirect:/tickets/" + id; + public String assignTicket(@PathVariable Long id, @ModelAttribute TicketAssignDTO dto) { + + ticketService.assignTicket(id, dto.getInvestigatorId()); + + return "redirect:/view/id/" + id; } //update status - // TODO implement DTO + service @PostMapping("/{id}/status") - public String updateStatus(@PathVariable Long id) { - return "redirect:/tickets/" + id; + @PreAuthorize("hasAnyRole('ADMIN','INVESTIGATOR')") + public String updateStatus(@PathVariable Long id, @ModelAttribute TicketStatusUpdateDTO dto) { + + ticketService.updateStatus(id, dto.getStatus()); + + return "redirect:/view/id/" + id; + } + + + @PreAuthorize("hasRole('REPORTER')") + @GetMapping("/my") + public String myTickets(Model model) { + + model.addAttribute("tickets", ticketService.getMyTickets()); + return "my-tickets"; + } + + @PreAuthorize("hasRole('INVESTIGATOR')") + @GetMapping("/assigned") + public String myAssignedTickets(Model model) { + + model.addAttribute("tickets", ticketService.getMyAssignedTickets()); + return "assigned-tickets"; } //create comment diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 4bfb1eb..1fbd447 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -3,15 +3,18 @@ import org.example.alfs.dto.ticket.TicketCreateDTO; import org.example.alfs.dto.ticket.TicketViewDTO; import org.example.alfs.entities.Ticket; +import org.example.alfs.entities.User; +import org.example.alfs.enums.Role; import org.example.alfs.enums.TicketStatus; import org.example.alfs.mapper.TicketMapper; import org.example.alfs.repositories.TicketRepository; +import org.example.alfs.repositories.UserRepository; +import org.example.alfs.security.SecurityUtils; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; - import java.util.List; -import java.util.Optional; + @Service public class TicketService { @@ -19,26 +22,34 @@ public class TicketService { private final TicketRepository ticketRepository; private final TicketMapper ticketMapper; + private final SecurityUtils securityUtils; + private final UserRepository userRepository; - public TicketService(TicketRepository ticketRepository, TicketMapper ticketMapper) { + public TicketService(TicketRepository ticketRepository, TicketMapper ticketMapper, SecurityUtils securityUtils, UserRepository userRepository) { this.ticketRepository = ticketRepository; this.ticketMapper = ticketMapper; + this.securityUtils = securityUtils; + this.userRepository = userRepository; } - //createNewTicket + //createNewTicket - need to be signed in atm - this should change later when we have anonymous access public TicketViewDTO createNewTicket(TicketCreateDTO ticketCreateDTO) { - Ticket ticket = new Ticket(); + Ticket ticket = new Ticket(); ticket.setTitle(ticketCreateDTO.getTitle()); ticket.setDescription(ticketCreateDTO.getDescription()); + User user = securityUtils.getCurrentUser(); + ticket.setReporter(user); + Ticket save = ticketRepository.save(ticket); return ticketMapper.entityToViewDTO(save); } - // View by token + // View by token - for anonymous users + // TODO: filter sensitive data for anonymous users (e.g. internal comments, investigator info) public TicketViewDTO getTicketByToken(String token) { Ticket ticket = ticketRepository.findByReporterToken(token). @@ -51,38 +62,140 @@ public TicketViewDTO getTicketById(Long id) { Ticket ticket = ticketRepository.findById(id). orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found")); + + // DEBUG START + System.out.println("==== GET TICKET DEBUG ===="); + System.out.println("Ticket ID: " + ticket.getId()); + System.out.println("Ticket reporter ID: " + + (ticket.getReporter() != null ? ticket.getReporter().getId() : "null")); + // DEBUG END + + checkAccess(ticket); + return ticketMapper.entityToViewDTO(ticket); } - //findByReporterId - public List getTicketsByReporterId(Long reporterId) { - return ticketRepository.findByReporterId(reporterId) + // Get all tickets for a reporter + public List getMyTickets() { + + User user = securityUtils.getCurrentUser(); + + return ticketRepository.findByReporterId(user.getId()) .stream() .map(ticketMapper::entityToViewDTO) .toList(); } - //findByInvestigatorId - public List getTicketsByInvestigatorId(Long investigatorId){ - return ticketRepository.findByInvestigatorId(investigatorId) - .stream() - .map(ticketMapper::entityToViewDTO) - .toList(); + // Get all tickets assigned to me - investigator + public List getMyAssignedTickets() { + + User user = securityUtils.getCurrentUser(); + + return ticketRepository.findByInvestigatorId(user.getId()) + .stream() + .map(ticketMapper::entityToViewDTO) + .toList(); + } + + + //findAll (pageable) + + // Assign ticket method + public void assignTicket(Long ticketId, Long investigatorId) { + + User currentUser = securityUtils.getCurrentUser(); + + // Only ADMIN can assign tickets + requireAdmin(currentUser); + + // Get ticket + Ticket ticket = ticketRepository.findById(ticketId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found")); + + // Get investigator user + User investigator = userRepository.findById(investigatorId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")); + + // Ensure user is actually an INVESTIGATOR + if (investigator.getRole() != Role.INVESTIGATOR) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "User is not an investigator"); + } + + if (ticket.getInvestigator() != null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Ticket already assigned"); + } + + // Assign ticket + ticket.setInvestigator(investigator); + + ticketRepository.save(ticket); + } + + + public void updateStatus(Long ticketId, TicketStatus status) { + + User user = securityUtils.getCurrentUser(); + + Ticket ticket = ticketRepository.findById(ticketId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found")); + + // ADMIN can update all tickets + if (user.getRole() == Role.ADMIN) { + ticket.setStatus(status); + ticketRepository.save(ticket); + return; + } + + // INVESTIGATOR can only update assigned tickets + if (user.getRole() == Role.INVESTIGATOR) { + if (ticket.getInvestigator() != null && + ticket.getInvestigator().getId().equals(user.getId())) { + + ticket.setStatus(status); + ticketRepository.save(ticket); + return; + } } + // All others denied + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); + } + + + // ----------------- filters ----------------- + //findByStatus - public List getTicketsByStatus(TicketStatus status) { - return ticketRepository.findByStatus(status) + public List getTicketsByStatus(TicketStatus status) { + + User user = securityUtils.getCurrentUser(); + + // Only ADMIN can get all tickets with a specific status + requireAdmin(user); + + return ticketRepository.findByStatus(status) + .stream() + .map(ticketMapper::entityToViewDTO) + .toList(); + } + + //findByStatusAndInvestigatorId + public List getTicketsByStatusAndInvestigator(TicketStatus status, Long investigatorId) { + + User user = securityUtils.getCurrentUser(); + + // ADMIN can see everything + if (user.getRole() == Role.ADMIN) { + return ticketRepository + .findByStatusAndInvestigatorId(status, investigatorId) .stream() .map(ticketMapper::entityToViewDTO) .toList(); } - //findByStatusAndInvestigatorId - public List getTicketsByStatusAndInvestigator( - TicketStatus status, - Long investigatorId) { + // INVESTIGATOR can only see their own tickets + if (user.getRole() == Role.INVESTIGATOR && + user.getId().equals(investigatorId)) { return ticketRepository .findByStatusAndInvestigatorId(status, investigatorId) @@ -91,5 +204,55 @@ public List getTicketsByStatusAndInvestigator( .toList(); } - //findAll (pageable) + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); + } + + + // ----------------- helpers ----------------- + private void checkAccess(Ticket ticket) { + + // Get the currently authenticated user from SecurityContext + User user = securityUtils.getCurrentUser(); + + // DEBUG START + System.out.println("==== ACCESS CHECK ===="); + System.out.println("Logged in user: " + user.getUsername() + " (id=" + user.getId() + ")"); + System.out.println("User role: " + user.getRole()); + + System.out.println("Ticket reporter: " + + (ticket.getReporter() != null ? ticket.getReporter().getId() : "null")); + + System.out.println("Ticket investigator: " + + (ticket.getInvestigator() != null ? ticket.getInvestigator().getId() : "null")); + System.out.println("======================"); + // DEBUG END + + // ADMIN - always allowed to access any ticket + if (user.getRole() == Role.ADMIN) return; + + // INVESTIGATOR - allowed only if the ticket is assigned to this user + if (user.getRole() == Role.INVESTIGATOR) { + if (ticket.getInvestigator() != null && + ticket.getInvestigator().getId().equals(user.getId())) { + return; + } + } + + // REPORTER - allowed only if the user is the creator of the ticket + if (user.getRole() == Role.REPORTER) { + if (ticket.getReporter() != null && + ticket.getReporter().getId().equals(user.getId())) { + return; + } + } + + // If none of the above conditions match -> deny access + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); + } + + private void requireAdmin(User user) { + if (user.getRole() != Role.ADMIN) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); + } + } } From ebaf30c4b7d14aae466f08a8d6ba0fc219e7422b Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Sat, 11 Apr 2026 17:48:16 +0200 Subject: [PATCH 2/7] updates updateticketstatus --- .../example/alfs/services/TicketService.java | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 12521c8..f0e3e49 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -155,20 +155,33 @@ private void requireAdmin(User user) { } } - // ----------------- status logic ----------------- + // ----------------- status logic ----------------- @Transactional public TicketViewDTO updateTicketStatus(Long id, TicketStatus newStatus) { User user = securityUtils.getCurrentUser(); - if (user.getRole() != Role.ADMIN && user.getRole() != Role.INVESTIGATOR) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); - } - Ticket ticket = ticketRepository.findById(id) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found")); + if (user.getRole() != Role.ADMIN) { + + if (user.getRole() == Role.INVESTIGATOR) { + + if (ticket.getInvestigator() == null || + !ticket.getInvestigator().getId().equals(user.getId())) { + + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); + } + + } else { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); + } + } + + // status logic + TicketStatus oldStatus = ticket.getStatus(); if (oldStatus == newStatus) { @@ -179,13 +192,17 @@ public TicketViewDTO updateTicketStatus(Long id, TicketStatus newStatus) { ALLOWED_TRANSITIONS.getOrDefault(ticket.getStatus(), Set.of()); if (!allowedTransitions.contains(newStatus)) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "Invalid transition from " + ticket.getStatus() + " to " + newStatus); + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Invalid transition from " + ticket.getStatus() + " to " + newStatus + ); } if (newStatus == TicketStatus.IN_PROGRESS && ticket.getInvestigator() == null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "Cannot move to IN_PROGRESS without investigator"); + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Cannot move to IN_PROGRESS without investigator" + ); } ticket.setStatus(newStatus); From af6904b831ca47162cd5ef2c12df36c27faa9ed7 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Sat, 11 Apr 2026 18:12:09 +0200 Subject: [PATCH 3/7] Added @Valid annotations in controller --- .../java/org/example/alfs/controllers/TicketController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/alfs/controllers/TicketController.java b/src/main/java/org/example/alfs/controllers/TicketController.java index fed8577..5e47799 100644 --- a/src/main/java/org/example/alfs/controllers/TicketController.java +++ b/src/main/java/org/example/alfs/controllers/TicketController.java @@ -74,7 +74,7 @@ public String viewTicketById(@PathVariable Long id, Model model) { //assign ticket @PreAuthorize("hasRole('ADMIN')") @PostMapping("/{id}/assign") - public String assignTicket(@PathVariable Long id, @ModelAttribute TicketAssignDTO dto) { + public String assignTicket(@PathVariable Long id, @Valid @ModelAttribute TicketAssignDTO dto) { ticketService.assignInvestigator(id, dto.getInvestigatorId()); @@ -84,7 +84,7 @@ public String assignTicket(@PathVariable Long id, @ModelAttribute TicketAssignDT //update status @PostMapping("/{id}/status") @PreAuthorize("hasAnyRole('ADMIN','INVESTIGATOR')") - public String updateStatus(@PathVariable Long id, @ModelAttribute TicketStatusUpdateDTO dto) { + public String updateStatus(@PathVariable Long id, @Valid @ModelAttribute TicketStatusUpdateDTO dto) { ticketService.updateTicketStatus(id, dto.getStatus()); From 1219c0b9c5934433287672f85ef08bb817042203 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Sat, 11 Apr 2026 18:12:40 +0200 Subject: [PATCH 4/7] Added null check for investigatorId, Improved error messages for assign investigator, minor cleanup --- .../example/alfs/services/TicketService.java | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index f0e3e49..79fbecc 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -189,7 +189,11 @@ public TicketViewDTO updateTicketStatus(Long id, TicketStatus newStatus) { } Set allowedTransitions = - ALLOWED_TRANSITIONS.getOrDefault(ticket.getStatus(), Set.of()); + ALLOWED_TRANSITIONS.get(ticket.getStatus()); + + if (allowedTransitions == null) { + allowedTransitions = Set.of(); + } if (!allowedTransitions.contains(newStatus)) { throw new ResponseStatusException( @@ -221,6 +225,13 @@ public TicketViewDTO updateTicketStatus(Long id, TicketStatus newStatus) { @Transactional public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { + if (investigatorId == null) { + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Investigator ID is required" + ); + } + User user = securityUtils.getCurrentUser(); requireAdmin(user); @@ -228,18 +239,27 @@ public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found")); if (ticket.getInvestigator() != null) { - throw new ResponseStatusException(HttpStatus.CONFLICT, "Already assigned"); + throw new ResponseStatusException( + HttpStatus.CONFLICT, + "Ticket already has an investigator assigned" + ); } if (ticket.getStatus() != TicketStatus.OPEN) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Must be OPEN"); + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Ticket must be in OPEN status to assign an investigator" + ); } User investigator = userRepository.findById(investigatorId) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Investigator not found")); if (investigator.getRole() != Role.INVESTIGATOR) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Not investigator"); + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "User is not an investigator" + ); } ticket.setInvestigator(investigator); @@ -270,8 +290,6 @@ public TicketViewDTO unassignInvestigator(Long id) { ticket.setInvestigator(null); ticket.setStatus(TicketStatus.OPEN); - Ticket savedTicket = ticketRepository.save(ticket); - - return ticketMapper.entityToViewDTO(savedTicket); + return ticketMapper.entityToViewDTO(ticketRepository.save(ticket)); } } \ No newline at end of file From 0c0e91931153ed0cc8c11edfc562cd3dcf92fa02 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Sat, 11 Apr 2026 18:29:15 +0200 Subject: [PATCH 5/7] fix: handle auth errors and restrict ticket status transitions - Return 401 instead of 500 when authentication fails - Disallow transition from RESOLVED to IN_PROGRESS --- .../example/alfs/services/TicketService.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 79fbecc..9174549 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -45,7 +45,7 @@ public TicketViewDTO createNewTicket(TicketCreateDTO ticketCreateDTO) { ticket.setTitle(ticketCreateDTO.getTitle()); ticket.setDescription(ticketCreateDTO.getDescription()); - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); ticket.setReporter(user); Ticket savedTicket = ticketRepository.save(ticket); @@ -73,7 +73,7 @@ public TicketViewDTO getTicketById(Long id) { // Get all tickets for a reporter public List getMyTickets() { - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); return ticketRepository.findByReporterId(user.getId()) .stream() @@ -83,7 +83,7 @@ public List getMyTickets() { // Get all tickets assigned to me public List getMyAssignedTickets() { - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); return ticketRepository.findByInvestigatorId(user.getId()) .stream() @@ -94,7 +94,7 @@ public List getMyAssignedTickets() { // ----------------- filters ----------------- public List getTicketsByStatus(TicketStatus status) { - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); requireAdmin(user); return ticketRepository.findByStatus(status) @@ -104,7 +104,7 @@ public List getTicketsByStatus(TicketStatus status) { } public List getTicketsByStatusAndInvestigator(TicketStatus status, Long investigatorId) { - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); if (user.getRole() == Role.ADMIN) { return ticketRepository.findByStatusAndInvestigatorId(status, investigatorId) @@ -128,7 +128,7 @@ public List getTicketsByStatusAndInvestigator(TicketStatus status // ----------------- helpers ----------------- private void checkAccess(Ticket ticket) { - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); if (user.getRole() == Role.ADMIN) return; @@ -155,12 +155,24 @@ private void requireAdmin(User user) { } } + private User requireCurrentUser() { + try { + return securityUtils.getCurrentUser(); + } catch (RuntimeException ex) { + throw new ResponseStatusException( + HttpStatus.UNAUTHORIZED, + "Authentication required", + ex + ); + } + } + // ----------------- status logic ----------------- @Transactional public TicketViewDTO updateTicketStatus(Long id, TicketStatus newStatus) { - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); Ticket ticket = ticketRepository.findById(id) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found")); @@ -218,10 +230,11 @@ public TicketViewDTO updateTicketStatus(Long id, TicketStatus newStatus) { private static final Map> ALLOWED_TRANSITIONS = Map.of( TicketStatus.OPEN, Set.of(TicketStatus.IN_PROGRESS), TicketStatus.IN_PROGRESS, Set.of(TicketStatus.RESOLVED), - TicketStatus.RESOLVED, Set.of(TicketStatus.CLOSED, TicketStatus.IN_PROGRESS), + TicketStatus.RESOLVED, Set.of(TicketStatus.CLOSED), TicketStatus.CLOSED, Set.of() ); + @Transactional public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { @@ -232,7 +245,7 @@ public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { ); } - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); requireAdmin(user); Ticket ticket = ticketRepository.findById(id) @@ -273,7 +286,7 @@ public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { @Transactional public TicketViewDTO unassignInvestigator(Long id) { - User user = securityUtils.getCurrentUser(); + User user = requireCurrentUser(); requireAdmin(user); Ticket ticket = ticketRepository.findById(id) From ed7a1caec0b871a09b14fead6798b3c5083f6c75 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Sat, 11 Apr 2026 18:40:03 +0200 Subject: [PATCH 6/7] fix: avoid converting all runtime exceptions to 401 in auth handling --- .../example/alfs/services/TicketService.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 9174549..685943b 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -159,11 +159,22 @@ private User requireCurrentUser() { try { return securityUtils.getCurrentUser(); } catch (RuntimeException ex) { - throw new ResponseStatusException( - HttpStatus.UNAUTHORIZED, - "Authentication required", - ex - ); + + String message = ex.getMessage(); + + boolean authFailure = + "No authenticated user in security context".equals(message) || + "Authenticated user not found in database".equals(message); + + if (authFailure) { + throw new ResponseStatusException( + HttpStatus.UNAUTHORIZED, + "Authentication required", + ex + ); + } + + throw ex; } } From 702ecfb485c22b594b8e294ebaffd391dcccde03 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Mon, 13 Apr 2026 11:55:46 +0200 Subject: [PATCH 7/7] Refactor: use savedTicket variable for consistency --- src/main/java/org/example/alfs/services/TicketService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 685943b..42c87ab 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -314,6 +314,7 @@ public TicketViewDTO unassignInvestigator(Long id) { ticket.setInvestigator(null); ticket.setStatus(TicketStatus.OPEN); - return ticketMapper.entityToViewDTO(ticketRepository.save(ticket)); + Ticket savedTicket = ticketRepository.save(ticket); + return ticketMapper.entityToViewDTO(savedTicket); } } \ No newline at end of file