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
86 changes: 86 additions & 0 deletions src/main/java/org/example/alfs/controllers/TicketController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.example.alfs.controllers;

import jakarta.validation.Valid;
import org.example.alfs.dto.ticket.TicketCreateDTO;
import org.example.alfs.dto.ticket.TicketViewDTO;
import org.example.alfs.entities.Ticket;
import org.example.alfs.services.TicketService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

//TODO: Decide final routes and redirects

@Controller
@RequestMapping
public class TicketController {

TicketService ticketService;


public TicketController(TicketService ticketService) {
this.ticketService = ticketService;

}

//create ticket

@GetMapping("/create")
public String createNewTicketForm(Model model) {
model.addAttribute("ticket", new TicketCreateDTO());
return "create";
}

//TODO REDIRECT, WHERE??
@PostMapping("/create")
public String createNewTicket(@ModelAttribute("ticket") @Valid TicketCreateDTO ticketCreateDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "create";
}

ticketService.createNewTicket(ticketCreateDTO);

return "redirect:/home";

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

//view ticket by token

@GetMapping("/view/token/{token}")
public String viewTicketByToken(@PathVariable String token, Model model) {

TicketViewDTO ticket = ticketService.getTicketByToken(token);
model.addAttribute("ticket", ticket);

return "view";
}

//view ticket by id
@GetMapping("/view/id/{id}")
public String viewTicketById(@PathVariable Long id, Model model) {

TicketViewDTO ticket = ticketService.getTicketById(id);
model.addAttribute("ticket", ticket);

return "view";
}
Comment on lines +51 to +67

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 | 🟠 Major

Map service “not found” failures to HTTP 404 instead of server errors.

On Line 53 and Line 63, service calls can throw RuntimeException("Ticket not found"); without handling, this typically surfaces as 500 instead of a not-found response.

🐛 Proposed controller-level fix
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
...
     `@GetMapping`("/view/token/{token}")
     public String viewTicketByToken(`@PathVariable` String token, Model model) {
-
-        TicketViewDTO ticket = ticketService.getTicketByToken(token);
+        TicketViewDTO ticket;
+        try {
+            ticket = ticketService.getTicketByToken(token);
+        } catch (RuntimeException ex) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found", ex);
+        }
         model.addAttribute("ticket", ticket);
 
         return "view";
     }
...
     `@GetMapping`("/view/id/{id}")
     public String viewTicketById(`@PathVariable` Long id, Model model) {
-
-        TicketViewDTO ticket = ticketService.getTicketById(id);
+        TicketViewDTO ticket;
+        try {
+            ticket = ticketService.getTicketById(id);
+        } catch (RuntimeException ex) {
+            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found", ex);
+        }
         model.addAttribute("ticket", ticket);
 
         return "view";
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public String viewTicketByToken(@PathVariable String token, Model model) {
TicketViewDTO ticket = ticketService.getTicketByToken(token);
model.addAttribute("ticket", ticket);
return "view";
}
//view ticket by id
@GetMapping("/view/id/{id}")
public String viewTicketById(@PathVariable Long id, Model model) {
TicketViewDTO ticket = ticketService.getTicketById(id);
model.addAttribute("ticket", ticket);
return "view";
}
public String viewTicketByToken(`@PathVariable` String token, Model model) {
TicketViewDTO ticket;
try {
ticket = ticketService.getTicketByToken(token);
} catch (RuntimeException ex) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found", ex);
}
model.addAttribute("ticket", ticket);
return "view";
}
//view ticket by id
`@GetMapping`("/view/id/{id}")
public String viewTicketById(`@PathVariable` Long id, Model model) {
TicketViewDTO ticket;
try {
ticket = ticketService.getTicketById(id);
} catch (RuntimeException ex) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found", ex);
}
model.addAttribute("ticket", ticket);
return "view";
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/alfs/controllers/TicketController.java` around
lines 51 - 67, The controller currently lets
ticketService.getTicketByToken(token) and ticketService.getTicketById(id)
propagate RuntimeException("Ticket not found") which yields 500; update
viewTicketByToken and viewTicketById to catch that specific not-found
RuntimeException (or detect by message/type) and convert it to an HTTP 404
response (e.g., by throwing new ResponseStatusException(HttpStatus.NOT_FOUND,
"Ticket not found") or setting response status and returning an error view).
Ensure you reference the service calls ticketService.getTicketByToken and
ticketService.getTicketById and preserve existing behavior for other exceptions.


//assign ticket
// TODO implement DTO + service
@PostMapping("/{id}/assign")
public String assignTicket(@PathVariable Long id) {
return "redirect:/tickets/" + id;
}

//update status
// TODO implement DTO + service
@PostMapping("/{id}/status")
public String updateStatus(@PathVariable Long id) {
return "redirect:/tickets/" + id;
}
Comment on lines +71 to +81

@coderabbitai coderabbitai Bot Apr 9, 2026

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

Stub methods redirect to non-existent routes.

Both assignTicket and updateStatus redirect to /tickets/{id}, but this controller has no mapping for that path. The existing view endpoint is at /view/id/{id}. This will cause 404 errors when these stubs are invoked.

🐛 Fix redirect URLs to match existing routes
     `@PostMapping`("/{id}/assign")
     public String assignTicket(`@PathVariable` Long id) {
-        return "redirect:/tickets/" + id;
+        return "redirect:/view/id/" + id;
     }
 
     //update status
     // TODO implement DTO + service
     `@PostMapping`("/{id}/status")
     public String updateStatus(`@PathVariable` Long id) {
-        return "redirect:/tickets/" + id;
+        return "redirect:/view/id/" + id;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@PostMapping("/{id}/assign")
public String assignTicket(@PathVariable Long id) {
return "redirect:/tickets/" + id;
}
//update status
// TODO implement DTO + service
@PostMapping("/{id}/status")
public String updateStatus(@PathVariable Long id) {
return "redirect:/tickets/" + id;
}
`@PostMapping`("/{id}/assign")
public String assignTicket(`@PathVariable` Long id) {
return "redirect:/view/id/" + id;
}
//update status
// TODO implement DTO + service
`@PostMapping`("/{id}/status")
public String updateStatus(`@PathVariable` Long id) {
return "redirect:/view/id/" + id;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/alfs/controllers/TicketController.java` around
lines 71 - 81, The two stub methods assignTicket and updateStatus currently
redirect to "/tickets/{id}" which does not exist and causes 404s; update their
redirect targets to the actual view endpoint used by this controller
("/view/id/{id}") so both methods return "redirect:/view/id/" + id; keep the
TODO about implementing DTO/service but ensure the immediate fix is to change
the redirect strings in assignTicket and updateStatus to point to
"/view/id/{id}".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This will be fixed later since we don't really know yet where to redirect

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@addee1, understood! Since the final routes are still being decided (and the existing //TODO: Decide final routes and redirects comment already captures that intent), keeping the placeholder redirects for now makes sense. Just make sure to update them once the routing is finalized to avoid any 404 errors in testing.


🧠 Learnings used
Learnt from: FionaSprinkles
Repo: ithsjava25/project-backend-alfs PR: 11
File: src/main/java/org/example/alfs/services/TicketService.java:50-54
Timestamp: 2026-04-09T13:26:47.188Z
Learning: In `ithsjava25/project-backend-alfs`, the IDOR vulnerability in `TicketService.getTicketById()` (and the `GET /view/id/{id}` endpoint in `TicketController`) is a known, intentionally deferred issue. Authorization/ownership checks will be added in a future PR when authentication and authorization are implemented. Do not re-flag this as a new issue.


//create comment
//View comment
//upload attachment
}
28 changes: 28 additions & 0 deletions src/main/java/org/example/alfs/mapper/TicketMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.example.alfs.mapper;

import org.example.alfs.dto.ticket.TicketViewDTO;
import org.example.alfs.entities.Ticket;
import org.springframework.stereotype.Component;

@Component
public class TicketMapper {

public TicketViewDTO entityToViewDTO(Ticket ticket){

TicketViewDTO ticketViewDTO = new TicketViewDTO();

ticketViewDTO.setId(ticket.getId());
ticketViewDTO.setTitle(ticket.getTitle());
ticketViewDTO.setDescription(ticket.getDescription());
ticketViewDTO.setStatus(ticket.getStatus());
ticketViewDTO.setCreatedAt(ticket.getCreatedAt());

if (ticket.getInvestigator() != null) {
ticketViewDTO.setAssignedInvestigatorId(ticket.getInvestigator().getId());
}

return ticketViewDTO;
}


}
95 changes: 95 additions & 0 deletions src/main/java/org/example/alfs/services/TicketService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.example.alfs.services;

import org.example.alfs.dto.ticket.TicketCreateDTO;
import org.example.alfs.dto.ticket.TicketViewDTO;
import org.example.alfs.entities.Ticket;
import org.example.alfs.enums.TicketStatus;
import org.example.alfs.mapper.TicketMapper;
import org.example.alfs.repositories.TicketRepository;
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 {


private final TicketRepository ticketRepository;
private final TicketMapper ticketMapper;

public TicketService(TicketRepository ticketRepository, TicketMapper ticketMapper) {
this.ticketRepository = ticketRepository;
this.ticketMapper = ticketMapper;
}

//createNewTicket
public TicketViewDTO createNewTicket(TicketCreateDTO ticketCreateDTO) {

Ticket ticket = new Ticket();

ticket.setTitle(ticketCreateDTO.getTitle());
ticket.setDescription(ticketCreateDTO.getDescription());

Ticket save = ticketRepository.save(ticket);

return ticketMapper.entityToViewDTO(save);
}

// View by token
public TicketViewDTO getTicketByToken(String token) {

Ticket ticket = ticketRepository.findByReporterToken(token).
orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found"));
return ticketMapper.entityToViewDTO(ticket);
}

//findById
public TicketViewDTO getTicketById(Long id) {

Ticket ticket = ticketRepository.findById(id).
orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found"));
return ticketMapper.entityToViewDTO(ticket);
Comment on lines +50 to +54

@coderabbitai coderabbitai Bot Apr 9, 2026

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate controller(s) and inspect mappings + security annotations around getTicketById usage
fd -i "TicketController.java" src
rg -n -C3 'getTicketById|@GetMapping|@RequestMapping|/view|/ticket|@PreAuthorize|@Secured|@RolesAllowed' src/main/java/org/example/alfs/controllers

# 2) Inspect Spring Security configuration for route-level protections
fd -i "SecurityConfig.java" src
rg -n -C3 'SecurityFilterChain|authorizeHttpRequests|requestMatchers|permitAll|authenticated|hasRole|hasAnyRole' src/main/java

# Expected:
# - Endpoints calling getTicketById should require authenticated/authorized access
#   (or enforce ownership checks before returning data).

Repository: ithsjava25/project-backend-alfs

Length of output: 5261


🏁 Script executed:

cat -n src/main/java/org/example/alfs/controllers/TicketController.java

Repository: ithsjava25/project-backend-alfs

Length of output: 2960


🏁 Script executed:

# Search for Spring Security configuration
find src -name "*Security*" -o -name "*Config*.java" | head -20

Repository: ithsjava25/project-backend-alfs

Length of output: 132


🏁 Script executed:

# Check if there are any authentication/authorization annotations used elsewhere in the project
rg -l '@PreAuthorize|@Secured|@RolesAllowed' src/main/java

Repository: ithsjava25/project-backend-alfs

Length of output: 57


🏁 Script executed:

find src -name "application*.properties" -o -name "application*.yml" -o -name "application*.yaml"

Repository: ithsjava25/project-backend-alfs

Length of output: 116


🏁 Script executed:

# Look for main application class
find src -name "*Application.java"

Repository: ithsjava25/project-backend-alfs

Length of output: 126


🏁 Script executed:

# Search for custom filters, interceptors, or authentication setup
rg -l 'FilterChain|Filter|Interceptor|SecurityFilterChain|HttpSecurity|Authentication' src/main/java

Repository: ithsjava25/project-backend-alfs

Length of output: 57


🏁 Script executed:

# Check pom.xml or build.gradle for Spring Security dependency
find . -maxdepth 2 \( -name "pom.xml" -o -name "build.gradle" \)

Repository: ithsjava25/project-backend-alfs

Length of output: 84


🏁 Script executed:

cat -n src/main/resources/application.properties

Repository: ithsjava25/project-backend-alfs

Length of output: 146


🏁 Script executed:

cat -n src/main/java/org/example/alfs/AlfsApplication.java

Repository: ithsjava25/project-backend-alfs

Length of output: 482


🏁 Script executed:

# Check for Spring Security in pom.xml
grep -A2 -B2 'spring-security' ./pom.xml

Repository: ithsjava25/project-backend-alfs

Length of output: 57


🏁 Script executed:

# Look for any SecurityConfig bean or configuration class
rg -n '@Configuration|@EnableWebSecurity|@Bean.*Security' src/main/java

Repository: ithsjava25/project-backend-alfs

Length of output: 149


🏁 Script executed:

cat -n src/main/java/org/example/alfs/config/PasswordConfig.java

Repository: ithsjava25/project-backend-alfs

Length of output: 687


🏁 Script executed:

# Check TicketService.getTicketById() for any access control logic
cat -n src/main/java/org/example/alfs/services/TicketService.java | grep -A10 "getTicketById"

Repository: ithsjava25/project-backend-alfs

Length of output: 581


🏁 Script executed:

# Check if pom.xml has spring-security dependency at all
cat -n ./pom.xml | head -50

Repository: ithsjava25/project-backend-alfs

Length of output: 2144


Confirm IDOR exposure: endpoint allows unrestricted ticket access by numeric ID.

The GET /view/id/{id} endpoint (TicketController line 60–66) retrieves any ticket by numeric ID without ownership validation. The service method getTicketById() returns ticket data directly with no access checks. Although Spring Security is on the classpath, no SecurityFilterChain configuration or method-level security annotations exist to restrict or validate access. A user could enumerate ticket IDs to view other users' tickets. Add ownership checks or restrict ticket access to authenticated users with proper authorization logic (e.g., verify the requester owns or is assigned to the ticket).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/alfs/services/TicketService.java` around lines 50 -
54, getTicketById in TicketService currently returns any ticket by numeric id
without authorization checks, exposing an IDOR risk; modify getTicketById (and
the TicketController GET /view/id/{id} flow) to enforce ownership/authorization
by retrieving the authenticated principal (e.g., from SecurityContext),
validating that the current user is the ticket owner or assignee (or has an
appropriate role) before mapping and returning via ticketMapper.entityToViewDTO,
and throw a ResponseStatusException(HttpStatus.FORBIDDEN) when the check fails;
use ticketRepository to fetch the ticket first and compare its owner/assignee
fields to the authenticated user's identifier.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will be handled when authentication/authorization is implemented.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@FionaSprinkles, understood — that makes sense. Once authentication is in place, the ownership/authorization check can be wired into getTicketById (and related endpoints) naturally.

Would you like me to open a GitHub issue to track this so it doesn't get lost? It would serve as a reminder to add the IDOR protection once the auth layer is ready.


✏️ Learnings added
Learnt from: FionaSprinkles
Repo: ithsjava25/project-backend-alfs PR: 11
File: src/main/java/org/example/alfs/services/TicketService.java:50-54
Timestamp: 2026-04-09T13:26:47.188Z
Learning: In `ithsjava25/project-backend-alfs`, the IDOR vulnerability in `TicketService.getTicketById()` (and the `GET /view/id/{id}` endpoint in `TicketController`) is a known, intentionally deferred issue. Authorization/ownership checks will be added in a future PR when authentication and authorization are implemented. Do not re-flag this as a new issue.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: addee1
Repo: ithsjava25/project-backend-alfs PR: 9
File: src/main/java/org/example/alfs/config/SecurityConfig.java:27-29
Timestamp: 2026-04-09T09:49:11.596Z
Learning: In the project `ithsjava25/project-backend-alfs`, the Spring Security configuration (`src/main/java/org/example/alfs/config/SecurityConfig.java`) intentionally disables CSRF and frame options, and permits the H2 console without authentication. This is by design for development-only use — the project is a stateless JWT-based REST API not targeting production deployment. Do not flag these settings as security issues.


}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

//findByReporterId
public List<TicketViewDTO> getTicketsByReporterId(Long reporterId) {
return ticketRepository.findByReporterId(reporterId)
.stream()
.map(ticketMapper::entityToViewDTO)
.toList();
}

//findByInvestigatorId
public List<TicketViewDTO> getTicketsByInvestigatorId(Long investigatorId){
return ticketRepository.findByInvestigatorId(investigatorId)
.stream()
.map(ticketMapper::entityToViewDTO)
.toList();
}

//findByStatus
public List<TicketViewDTO> getTicketsByStatus(TicketStatus status) {
return ticketRepository.findByStatus(status)
.stream()
.map(ticketMapper::entityToViewDTO)
.toList();
}

//findByStatusAndInvestigatorId
public List<TicketViewDTO> getTicketsByStatusAndInvestigator(
TicketStatus status,
Long investigatorId) {

return ticketRepository
.findByStatusAndInvestigatorId(status, investigatorId)
.stream()
.map(ticketMapper::entityToViewDTO)
.toList();
}

//findAll (pageable)
}
Loading