Files
tinker_tickets/api/add_comment.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

129 lines
4.0 KiB
PHP

<?php
// Disable error display in the output
ini_set('display_errors', 0);
error_reporting(E_ALL);
// Apply rate limiting
require_once dirname(__DIR__) . '/middleware/RateLimitMiddleware.php';
RateLimitMiddleware::apply('api');
// Start output buffering to capture any errors
ob_start();
try {
// Include required files with proper error handling
$configPath = dirname(__DIR__) . '/config/config.php';
$commentModelPath = dirname(__DIR__) . '/models/CommentModel.php';
$auditLogModelPath = dirname(__DIR__) . '/models/AuditLogModel.php';
if (!file_exists($configPath)) {
throw new Exception("Config file not found: $configPath");
}
if (!file_exists($commentModelPath)) {
throw new Exception("CommentModel file not found: $commentModelPath");
}
require_once $configPath;
require_once $commentModelPath;
require_once $auditLogModelPath;
require_once dirname(__DIR__) . '/helpers/Database.php';
// Check authentication via session
session_start();
if (!isset($_SESSION['user']) || !isset($_SESSION['user']['user_id'])) {
throw new Exception("Authentication required");
}
// CSRF Protection
require_once dirname(__DIR__) . '/middleware/CsrfMiddleware.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!CsrfMiddleware::validateToken($csrfToken)) {
http_response_code(403);
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Invalid CSRF token']);
exit;
}
}
$currentUser = $_SESSION['user'];
$userId = $currentUser['user_id'];
// Use centralized database connection
$conn = Database::getConnection();
// Get POST data
$data = json_decode(file_get_contents('php://input'), true);
if (!$data) {
throw new Exception("Invalid JSON data received");
}
$ticketId = $data['ticket_id'];
// Initialize models
$commentModel = new CommentModel($conn);
$auditLog = new AuditLogModel($conn);
// Extract @mentions from comment text
$mentions = $commentModel->extractMentions($data['comment_text'] ?? '');
$mentionedUsers = [];
if (!empty($mentions)) {
$mentionedUsers = $commentModel->getMentionedUsers($mentions);
}
// Add comment with user tracking
$result = $commentModel->addComment($ticketId, $data, $userId);
// Log comment creation to audit log
if ($result['success'] && isset($result['comment_id'])) {
$auditLog->logCommentCreate($userId, $result['comment_id'], $ticketId);
// Log mentions to audit log
foreach ($mentionedUsers as $mentionedUser) {
$auditLog->log(
$userId,
'mention',
'user',
(string)$mentionedUser['user_id'],
[
'ticket_id' => $ticketId,
'comment_id' => $result['comment_id'],
'mentioned_username' => $mentionedUser['username']
]
);
}
// Add mentioned users to result for frontend
$result['mentions'] = array_map(function($u) {
return $u['username'];
}, $mentionedUsers);
}
// Add user display name to result for frontend
if ($result['success']) {
$result['user_name'] = $currentUser['display_name'] ?? $currentUser['username'];
}
// Discard any unexpected output
ob_end_clean();
// Return JSON response
header('Content-Type: application/json');
echo json_encode($result);
} catch (Exception $e) {
// Discard any unexpected output
ob_end_clean();
// Log error details but don't expose to client
error_log("Add comment API error: " . $e->getMessage());
// Return error response
header('Content-Type: application/json');
echo json_encode([
'success' => false,
'error' => 'An internal error occurred'
]);
}