Extract ticket assignment from assign_ticket.php into AssignmentService (#111)
The access check, the admin/creator/current-assignee permission rule, unassign/assign, the audit log, the optional Matrix assignment notification and the stats-cache invalidation move into services/AssignmentService.php for reuse by the MCP assign_ticket tool. Error messages and status codes are unchanged. One deliberate difference: assign_ticket.php's early error responses (400/403/404) used a bare echo and so omitted the CSRF token that bootstrap had just rotated. Every response now goes through apiRespond(), which includes it. The front end already resyncs its token from any response body, so this is compatible, and a failed assign can no longer leave the page holding a stale token. Verified over real HTTP: the assignee can reassign (200); a user who can see the ticket but isn't admin/creator/assignee gets 403 'Permission denied'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
This commit is contained in:
+9
-79
@@ -1,11 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/bootstrap.php';
|
||||
require_once dirname(__DIR__) . '/models/TicketModel.php';
|
||||
require_once dirname(__DIR__) . '/models/AuditLogModel.php';
|
||||
require_once dirname(__DIR__) . '/models/UserModel.php';
|
||||
require_once dirname(__DIR__) . '/helpers/NotificationHelper.php';
|
||||
require_once dirname(__DIR__) . '/helpers/SynapseHelper.php';
|
||||
require_once dirname(__DIR__) . '/services/AssignmentService.php';
|
||||
|
||||
// Get request data
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
@@ -15,79 +11,13 @@ if (!is_array($data)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$ticketIdRaw = isset($data['ticket_id']) ? trim((string)$data['ticket_id']) : '';
|
||||
$assignedTo = $data['assigned_to'] ?? null;
|
||||
// Validation, permission check, audit log, notification and stats-cache
|
||||
// invalidation live in AssignmentService so the MCP assign_ticket tool runs
|
||||
// the same code path.
|
||||
$result = AssignmentService::assign($conn, $currentUser, $data);
|
||||
|
||||
if (!ctype_digit($ticketIdRaw) || (int)$ticketIdRaw <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Ticket ID required']);
|
||||
exit;
|
||||
}
|
||||
$ticketId = $ticketIdRaw;
|
||||
|
||||
$ticketModel = new TicketModel($conn);
|
||||
$auditLogModel = new AuditLogModel($conn);
|
||||
$userModel = new UserModel($conn);
|
||||
|
||||
// Verify ticket exists and user can access it
|
||||
$ticket = $ticketModel->getTicketById($ticketId);
|
||||
if (!$ticket || !$ticketModel->canUserAccessTicket($ticket, $currentUser)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Ticket not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Authorization: only admins or the ticket creator/assignee can reassign
|
||||
if (!$isAdmin && (int)$ticket['created_by'] !== (int)$userId && (int)$ticket['assigned_to'] !== (int)$userId) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'error' => 'Permission denied']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($assignedTo === null || $assignedTo === '') {
|
||||
// Unassign ticket
|
||||
$success = $ticketModel->unassignTicket($ticketId, $userId);
|
||||
if ($success) {
|
||||
$auditLogModel->log($userId, 'unassign', 'ticket', $ticketId);
|
||||
}
|
||||
} else {
|
||||
// Validate assigned_to is a valid user ID
|
||||
$assignedTo = (int)$assignedTo;
|
||||
$targetUser = $userModel->getUserById($assignedTo);
|
||||
if (!$targetUser) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid user ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Assign ticket
|
||||
$success = $ticketModel->assignTicket($ticketId, $assignedTo, $userId);
|
||||
if ($success) {
|
||||
$auditLogModel->log($userId, 'assign', 'ticket', $ticketId, ['assigned_to' => $assignedTo]);
|
||||
|
||||
if (!empty($GLOBALS['config']['MATRIX_NOTIFY_ASSIGNMENTS'])) {
|
||||
$changedByDisplay = $currentUser['display_name'] ?? $currentUser['username'] ?? null;
|
||||
$assigneeName = $targetUser['display_name'] ?? $targetUser['username'] ?? null;
|
||||
$assigneeMatrix = isset($targetUser['username'])
|
||||
? SynapseHelper::resolveUsername($targetUser['username'])
|
||||
: null;
|
||||
NotificationHelper::sendAssignmentNotification(
|
||||
$ticketId,
|
||||
$ticket['title'] ?? "Ticket #{$ticketId}",
|
||||
$assigneeName,
|
||||
$assigneeMatrix,
|
||||
$changedByDisplay,
|
||||
$ticket['visibility'] ?? 'public'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
http_response_code(500);
|
||||
apiRespond(['success' => false, 'error' => 'Failed to update ticket assignment']);
|
||||
} else {
|
||||
require_once dirname(__DIR__) . '/models/StatsModel.php';
|
||||
(new StatsModel($conn))->invalidateCache();
|
||||
apiRespond(['success' => true]);
|
||||
if (!empty($result['http_status'])) {
|
||||
http_response_code($result['http_status']);
|
||||
unset($result['http_status']);
|
||||
}
|
||||
apiRespond($result);
|
||||
|
||||
Reference in New Issue
Block a user