Files
tinker_tickets/api/manage_workflows.php
Jared Vititoe ed9c2a39d1 Fix error message disclosure in API endpoints
Replace exception getMessage() exposure with generic error messages
to prevent internal information disclosure. Errors are now logged
with full details while clients receive sanitized responses.

Affected endpoints:
- add_comment, update_comment, delete_comment
- update_ticket, export_tickets
- generate_api_key, revoke_api_key
- manage_templates, manage_workflows, manage_recurring
- custom_fields, get_users

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 18:56:29 -05:00

188 lines
7.1 KiB
PHP

<?php
/**
* Workflow/Status Transitions Management API
* CRUD operations for status_transitions table
*/
ini_set('display_errors', 0);
error_reporting(E_ALL);
require_once dirname(__DIR__) . '/middleware/RateLimitMiddleware.php';
require_once dirname(__DIR__) . '/models/WorkflowModel.php';
RateLimitMiddleware::apply('api');
try {
require_once dirname(__DIR__) . '/config/config.php';
require_once dirname(__DIR__) . '/helpers/Database.php';
require_once dirname(__DIR__) . '/models/AuditLogModel.php';
// Check authentication
session_start();
if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'Authentication required']);
exit;
}
// Check admin privileges
if (!$_SESSION['user']['is_admin']) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Admin privileges required']);
exit;
}
// CSRF Protection for write operations
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
require_once dirname(__DIR__) . '/middleware/CsrfMiddleware.php';
$csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!CsrfMiddleware::validateToken($csrfToken)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']);
exit;
}
}
// Use centralized database connection
$conn = Database::getConnection();
// Initialize audit log
$auditLog = new AuditLogModel($conn);
$userId = $_SESSION['user']['user_id'];
header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];
$id = isset($_GET['id']) ? (int)$_GET['id'] : null;
switch ($method) {
case 'GET':
if ($id) {
// Get single transition
$stmt = $conn->prepare("SELECT * FROM status_transitions WHERE transition_id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();
$transition = $result->fetch_assoc();
$stmt->close();
echo json_encode(['success' => true, 'transition' => $transition]);
} else {
// Get all transitions
$result = $conn->query("SELECT * FROM status_transitions ORDER BY from_status, to_status");
$transitions = [];
while ($row = $result->fetch_assoc()) {
$transitions[] = $row;
}
echo json_encode(['success' => true, 'transitions' => $transitions]);
}
break;
case 'POST':
$data = json_decode(file_get_contents('php://input'), true);
$stmt = $conn->prepare("INSERT INTO status_transitions (from_status, to_status, requires_comment, requires_admin, is_active)
VALUES (?, ?, ?, ?, ?)");
$stmt->bind_param('ssiii',
$data['from_status'],
$data['to_status'],
$data['requires_comment'] ?? 0,
$data['requires_admin'] ?? 0,
$data['is_active'] ?? 1
);
if ($stmt->execute()) {
$transitionId = $conn->insert_id;
WorkflowModel::clearCache(); // Clear workflow cache
// Audit log: workflow transition created
$auditLog->log($userId, 'create', 'workflow_transition', (string)$transitionId, [
'from_status' => $data['from_status'],
'to_status' => $data['to_status'],
'requires_comment' => $data['requires_comment'] ?? 0,
'requires_admin' => $data['requires_admin'] ?? 0
]);
echo json_encode(['success' => true, 'transition_id' => $transitionId]);
} else {
error_log("Workflow creation failed: " . $stmt->error);
echo json_encode(['success' => false, 'error' => 'Failed to create workflow transition']);
}
$stmt->close();
break;
case 'PUT':
if (!$id) {
echo json_encode(['success' => false, 'error' => 'ID required']);
exit;
}
$data = json_decode(file_get_contents('php://input'), true);
$stmt = $conn->prepare("UPDATE status_transitions SET
from_status = ?, to_status = ?, requires_comment = ?, requires_admin = ?, is_active = ?
WHERE transition_id = ?");
$stmt->bind_param('ssiiii',
$data['from_status'],
$data['to_status'],
$data['requires_comment'] ?? 0,
$data['requires_admin'] ?? 0,
$data['is_active'] ?? 1,
$id
);
$success = $stmt->execute();
if ($success) {
WorkflowModel::clearCache(); // Clear workflow cache
// Audit log: workflow transition updated
$auditLog->log($userId, 'update', 'workflow_transition', (string)$id, [
'from_status' => $data['from_status'],
'to_status' => $data['to_status'],
'requires_comment' => $data['requires_comment'] ?? 0,
'requires_admin' => $data['requires_admin'] ?? 0
]);
}
echo json_encode(['success' => $success]);
$stmt->close();
break;
case 'DELETE':
if (!$id) {
echo json_encode(['success' => false, 'error' => 'ID required']);
exit;
}
// Get transition details before deletion for audit log
$getStmt = $conn->prepare("SELECT from_status, to_status FROM status_transitions WHERE transition_id = ?");
$getStmt->bind_param('i', $id);
$getStmt->execute();
$getResult = $getStmt->get_result();
$transitionData = $getResult->fetch_assoc();
$getStmt->close();
$stmt = $conn->prepare("DELETE FROM status_transitions WHERE transition_id = ?");
$stmt->bind_param('i', $id);
$success = $stmt->execute();
if ($success) {
WorkflowModel::clearCache(); // Clear workflow cache
// Audit log: workflow transition deleted
$auditLog->log($userId, 'delete', 'workflow_transition', (string)$id, [
'from_status' => $transitionData['from_status'] ?? 'unknown',
'to_status' => $transitionData['to_status'] ?? 'unknown'
]);
}
echo json_encode(['success' => $success]);
$stmt->close();
break;
default:
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
}
} catch (Exception $e) {
error_log("Workflow API error: " . $e->getMessage());
http_response_code(500);
echo json_encode(['success' => false, 'error' => 'An internal error occurred']);
}