ensure random people cant cancel jobs

This commit is contained in:
Anthony Stirling 2025-06-19 12:28:12 +01:00
parent 4f5236fa82
commit 563948691e
3 changed files with 116 additions and 42 deletions

View File

@ -102,6 +102,22 @@ public class JobExecutorService {
// Store the job ID in the request for potential use by other components
if (request != null) {
request.setAttribute("jobId", jobId);
// Also track this job ID in the user's session for authorization purposes
// This ensures users can only cancel their own jobs
if (request.getSession() != null) {
@SuppressWarnings("unchecked")
java.util.Set<String> userJobIds = (java.util.Set<String>)
request.getSession().getAttribute("userJobIds");
if (userJobIds == null) {
userJobIds = new java.util.concurrent.ConcurrentSkipListSet<>();
request.getSession().setAttribute("userJobIds", userJobIds);
}
userJobIds.add(jobId);
log.debug("Added job ID {} to user session", jobId);
}
}
// Determine which timeout to use

View File

@ -0,0 +1,82 @@
package stirling.software.proprietary.controller;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.job.JobStats;
import stirling.software.common.service.JobQueue;
import stirling.software.common.service.TaskManager;
import stirling.software.proprietary.audit.AuditEventType;
import stirling.software.proprietary.audit.AuditLevel;
import stirling.software.proprietary.audit.Audited;
/**
* Admin controller for job management. These endpoints require admin privileges
* and provide insight into system jobs and queues.
*/
@RestController
@RequiredArgsConstructor
@Slf4j
public class AdminJobController {
private final TaskManager taskManager;
private final JobQueue jobQueue;
/**
* Get statistics about jobs in the system (admin only)
*
* @return Job statistics
*/
@GetMapping("/api/v1/admin/job/stats")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<JobStats> getJobStats() {
JobStats stats = taskManager.getJobStats();
log.info("Admin requested job stats: {} active, {} completed jobs",
stats.getActiveJobs(), stats.getCompletedJobs());
return ResponseEntity.ok(stats);
}
/**
* Get statistics about the job queue (admin only)
*
* @return Queue statistics
*/
@GetMapping("/api/v1/admin/job/queue/stats")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<?> getQueueStats() {
Map<String, Object> queueStats = jobQueue.getQueueStats();
log.info("Admin requested queue stats: {} queued jobs", queueStats.get("queuedJobs"));
return ResponseEntity.ok(queueStats);
}
/**
* Manually trigger cleanup of old jobs (admin only)
*
* @return A response indicating how many jobs were cleaned up
*/
@PostMapping("/api/v1/admin/job/cleanup")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<?> cleanupOldJobs() {
int beforeCount = taskManager.getJobStats().getTotalJobs();
taskManager.cleanupOldJobs();
int afterCount = taskManager.getJobStats().getTotalJobs();
int removedCount = beforeCount - afterCount;
log.info("Admin triggered job cleanup: removed {} jobs, {} remaining",
removedCount, afterCount);
return ResponseEntity.ok(
Map.of(
"message", "Cleanup complete",
"removedJobs", removedCount,
"remainingJobs", afterCount));
}
}

View File

@ -6,9 +6,10 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -27,6 +28,7 @@ public class JobController {
private final TaskManager taskManager;
private final FileStorage fileStorage;
private final JobQueue jobQueue;
private final HttpServletRequest request;
/**
* Get the status of a job
@ -98,50 +100,14 @@ public class JobController {
return ResponseEntity.ok(result.getResult());
}
/**
* Get statistics about jobs in the system
*
* @return Job statistics
*/
@GetMapping("/api/v1/general/job/stats")
public ResponseEntity<JobStats> getJobStats() {
JobStats stats = taskManager.getJobStats();
return ResponseEntity.ok(stats);
}
/**
* Get statistics about the job queue
*
* @return Queue statistics
*/
@GetMapping("/api/v1/general/job/queue/stats")
public ResponseEntity<?> getQueueStats() {
Map<String, Object> queueStats = jobQueue.getQueueStats();
return ResponseEntity.ok(queueStats);
}
/**
* Manually trigger cleanup of old jobs
*
* @return A response indicating how many jobs were cleaned up
*/
@PostMapping("/api/v1/general/job/cleanup")
public ResponseEntity<?> cleanupOldJobs() {
int beforeCount = taskManager.getJobStats().getTotalJobs();
taskManager.cleanupOldJobs();
int afterCount = taskManager.getJobStats().getTotalJobs();
int removedCount = beforeCount - afterCount;
return ResponseEntity.ok(
Map.of(
"message", "Cleanup complete",
"removedJobs", removedCount,
"remainingJobs", afterCount));
}
// Admin-only endpoints have been moved to AdminJobController in the proprietary package
/**
* Cancel a job by its ID
*
* This method should only allow cancellation of jobs that were created by the current user.
* The jobId should be part of the user's session or otherwise linked to their identity.
*
* @param jobId The job ID
* @return Response indicating whether the job was cancelled
*/
@ -149,6 +115,16 @@ public class JobController {
public ResponseEntity<?> cancelJob(@PathVariable("jobId") String jobId) {
log.debug("Request to cancel job: {}", jobId);
// Verify that this job belongs to the current user
// We can use the current request's session to validate ownership
Object sessionJobIds = request.getSession().getAttribute("userJobIds");
if (sessionJobIds == null || !(sessionJobIds instanceof java.util.Set) ||
!((java.util.Set<?>) sessionJobIds).contains(jobId)) {
// Either no jobs in session or jobId doesn't match user's jobs
log.warn("Unauthorized attempt to cancel job: {}", jobId);
return ResponseEntity.status(403).body(Map.of("message", "You are not authorized to cancel this job"));
}
// First check if the job is in the queue
boolean cancelled = false;
int queuePosition = -1;