Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.example.untitled.usercase.dto.CommentDto;
import org.example.untitled.usercase.dto.CreateCaseRequest;
import org.example.untitled.usercase.dto.CreateCommentRequest;
import org.example.untitled.usercase.AuditLog;
import org.example.untitled.usercase.service.AuditLogService;
import org.example.untitled.usercase.service.CaseService;
import org.example.untitled.usercase.service.CommentService;
import org.slf4j.Logger;
Expand All @@ -32,12 +34,14 @@ public class CaseController {

private final CaseService caseService;
private final CommentService commentService;
private final AuditLogService auditLogService;
private static final Logger log = LoggerFactory.getLogger(CaseController.class);


public CaseController(CaseService caseService, CommentService commentService) {
public CaseController(CaseService caseService, CommentService commentService, AuditLogService auditLogService) {
this.caseService = caseService;
this.commentService = commentService;
this.auditLogService = auditLogService;
}

@PostMapping
Expand Down Expand Up @@ -71,8 +75,10 @@ public String showTicketDetails(
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You do not own this ticket");

List<CommentDto> comments = commentService.getCommentsByTicketId(id);
List<AuditLog> auditLogs = auditLogService.getLogsForCase(id);
model.addAttribute("ticket", ticket);
model.addAttribute("comments", comments);
model.addAttribute("auditLogs", auditLogs);
return "ticket";
}

Expand Down
56 changes: 33 additions & 23 deletions src/main/resources/templates/ticket.html
Original file line number Diff line number Diff line change
@@ -1,49 +1,59 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
<html xmlns:th="https://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
lang="en">
<head>
<meta charset="UTF-8">
<title>Ticket</title>
<link rel="stylesheet" th:href="@{/style.css}" href="/style.css"/>
<link rel="stylesheet" th:href="@{/style.css}"/>
</head>
<body>
<div>
<div class="page-wrapper">
<h1>Ticket Details</h1>
<div th:object="${ticket}">
<div class="ticket-info">
<div class="ticket-title">
<p th:text="*{title}"></p>
<div class="ticket-header">
<span class="ticket-title" th:text="*{title}"></span>
<span class="status-badge"
th:text="*{status.displayName}"
th:classappend="'status-' + *{status.name().toLowerCase().replace('_', '-')}">
</span>
</div>
<div class="ticket-body">
<p>Status: </p>
<p th:text="*{status.displayName}"></p>
<p>Handler: </p>
<p th:text="*{assignedToUsername}"></p><br>
<p>Description: </p>
<p th:text="*{description}"></p><br>
<p>Handler: <span th:text="*{assignedToUsername} ?: 'Unassigned'"></span></p>
<p th:text="*{description}"></p>
</div>
</div>
</div>
<div>
<div class="ticket-comments">
<div th:each="comment : ${comments}" class="comment-box">
<p class="comment-text" th:text="${comment.text}"></p>
<p class="comment-author">
Author: <span th:text="${comment.authorUsername}"></span>
</p>

<div th:if="${not #lists.isEmpty(comments)}">
<h2>Comments</h2>
<div th:each="comment : ${comments}" class="ticket-info">
<p class="comment-text" th:text="${comment.text}"></p>
<p class="assigned">Author: <span th:text="${comment.authorUsername}"></span></p>
</div>
</div>

<div th:if="${not #lists.isEmpty(auditLogs)}">
<h2>Activity Log</h2>
<div th:each="entry : ${auditLogs}" class="ticket-info">
<div class="ticket-header">
<span class="ticket-title" th:text="${entry.action}"></span>
<span class="assigned" th:text="${#temporals.formatISO(entry.timestamp)}"></span>
</div>
</div>
</div>
<div class="ticket-footer">
<nav>
<a th:href="@{/tickets/{id}/close(id=${ticket.id})}">Close</a> |

<footer style="margin-top: 24px;">
<nav style="display: flex; gap: 10px;">
<a th:if="${ticket.status.name() != 'CLOSED' and ticket.status.name() != 'SOLVED'}"
th:href="@{/tickets/{id}/close(id=${ticket.id})}" class="btn btn-danger">Close</a>
Comment on lines +49 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Close button shown to non-owners will 403.

The visibility predicate only checks status, but CaseController.closeTicket (lines 118–131) throws 403 FORBIDDEN for anyone who is not the ticket owner. A HANDLER/SUPERVISOR/ADMIN viewing a ticket they don't own will see this "Close" button and get an error page when clicking it.

Consider also gating it on ownership, e.g. by exposing an isOwner flag from the controller or using Spring Security's sec:authorize together with a model attribute:

Proposed fix

In CaseController.showTicketDetails:

+        boolean isOwner = !caseService.isNotOwner(ticket, userDetails.getUsername());
         model.addAttribute("ticket", ticket);
         model.addAttribute("comments", comments);
         model.addAttribute("auditLogs", auditLogs);
+        model.addAttribute("isOwner", isOwner);

In ticket.html:

-            <a th:if="${ticket.status.name() != 'CLOSED' and ticket.status.name() != 'SOLVED'}"
-               th:href="@{/tickets/{id}/close(id=${ticket.id})}" class="btn btn-danger">Close</a>
+            <a th:if="${isOwner and ticket.status.name() != 'CLOSED' and ticket.status.name() != 'SOLVED'}"
+               th:href="@{/tickets/{id}/close(id=${ticket.id})}" class="btn btn-danger">Close</a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/ticket.html` around lines 47 - 48, The Close
button is shown to non-owners and will 403 when they click it; update the
ownership check in the view: set an isOwner boolean in
CaseController.showTicketDetails (compute by comparing ticket.getOwner().getId()
to current user id) and include it in the model, then change the th:if on the
Close link in ticket.html to require both the status check and isOwner (or
alternatively use Spring Security sec:authorize with a model attribute). Ensure
CaseController.closeTicket behavior remains unchanged but the UI only renders
the Close anchor when model attribute isOwner is true and ticket.status is not
CLOSED or SOLVED.

<a sec:authorize="hasAnyRole('HANDLER', 'SUPERVISOR', 'ADMIN')"
th:href="@{/handler}" class="btn btn-secondary">Back</a>
<a sec:authorize="hasRole('USER')"
th:href="@{/user}" class="btn btn-secondary">Back</a>
</nav>
</div>
</footer>
</div>
</body>
</html>
</html>