diff --git a/app/Config/Routes.php b/app/Config/Routes.php index cf17885b..b9988f4a 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -449,6 +449,8 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) { $routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend'); $routes->get('getTpaClaimDumpErrorData/(:any)', 'TicketServiceController::getTpaClaimDumpErrorData/$1'); $routes->get('croneDailyActivityReport', 'DashboardController::croneDailyActivityReport'); + $routes->get('insertSampleTpaApiData/(:any)', 'TestingController::insertSampleTpaApiData/$1'); + $routes->get('listEmployeeCountByClientPolicy', 'TestingController::listEmployeeCountByClientPolicy'); }); $routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail"); diff --git a/app/Controllers/AppContentManagementController.php b/app/Controllers/AppContentManagementController.php index c3bcde67..e13732b2 100755 --- a/app/Controllers/AppContentManagementController.php +++ b/app/Controllers/AppContentManagementController.php @@ -226,6 +226,14 @@ class AppContentManagementController extends AdminController if ($this->request->getMethod() === 'post') { + /** + * -------------------------------------------------------------------------- + * STEP 1: INITIAL VALIDATION + * -------------------------------------------------------------------------- + * These are the basic validation rules. For 'content' and 'notes', we only + * check if they are provided and within the allowed length. + * The more advanced security check for script tags happens next. + */ $rules = [ 'fe_id' => [ 'rules' => 'permit_empty|integer|is_natural', @@ -258,22 +266,20 @@ class AppContentManagementController extends AdminController 'regex_match' => 'Heading contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ], - 'content' => [ - 'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', + 'content' => [ + 'rules' => 'required|max_length[5000]', 'errors' => [ - 'required' => 'Content is required', - 'max_length' => 'Content cannot exceed 5000 characters', - 'regex_match' => 'Content contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' + 'required' => 'Content is required', + 'max_length' => 'Content cannot exceed 5000 characters', ] ], 'notes' => [ - 'rules' => 'required|max_length[1500]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', + 'rules' => 'required|max_length[1500]', 'errors' => [ - 'required' => 'Notes are required', - 'max_length' => 'Notes cannot exceed 1500 characters', - 'regex_match' => 'Notes contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' + 'required' => 'Notes are required', + 'max_length' => 'Notes cannot exceed 1500 characters', ] - ] + ], ]; if (!$this->validate($rules)) { @@ -284,9 +290,60 @@ class AppContentManagementController extends AdminController 'errors' => $this->validator->getErrors() ]); } - $request_post_data = $this->request->getPost(); - $data = sanitizeInputArrayAdvanced($request_post_data); - $id = $data['fe_id'] ?? null; + + /************************************************************************** + * REFACTORED SANITIZATION LOGIC (XSS Protection) + ************************************************************************** + * + * Per the user's request, we are avoiding the generic `sanitizeInputArrayAdvanced` + * on the `content` and `notes` fields, as they require special HTML + * handling. + * + * The new process is: + * 1. Get the raw `content` and `notes` directly from the POST request. + * 2. Perform the critical XSS validation on this raw content using `hasXssTags()`. + * If it fails, the request is rejected immediately. This satisfies all + * the failure test cases (Tests 4-9). + * 3. Take all *other* POST data and sanitize it using the generic + * `sanitizeInputArrayAdvanced` function. + * 4. Sanitize the now-validated `content` and `notes` using our specific + * `sanitizeHtml()` function, which allows safe HTML. + * 5. Combine the sanitized data into a final array for database insertion. + * + *************************************************************************/ + + // Step 1: Get raw `content` and `notes`. + $rawContent = $this->request->getPost('content'); + $rawNotes = $this->request->getPost('notes'); + + // Step 2: Perform critical XSS validation on raw input. + $xssErrors = []; + if ($this->hasXssTags($rawContent)) { + $xssErrors['content'] = 'Content contains restricted tags. Script, iframe and event handlers are not allowed'; + } + if ($this->hasXssTags($rawNotes)) { + $xssErrors['notes'] = 'Notes contains restricted tags. Script, iframe and event handlers are not allowed'; + } + + if (!empty($xssErrors)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => false, + 'message' => 'Input validation failed', + 'code' => 400, + 'errors' => $xssErrors + ]); + } + + // Step 3: Sanitize all *other* POST data. + $otherPostData = $this->request->getPost(); + unset($otherPostData['content'], $otherPostData['notes']); + $data = sanitizeInputArrayAdvanced($otherPostData); + + // Step 4 & 5: Sanitize and re-combine `content` and `notes`. + $data['content'] = $this->sanitizeHtml($rawContent); + $data['notes'] = $this->sanitizeHtml($rawNotes); + + $id = $data['fe_id'] ?? null; if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) { return $this->response->setStatusCode(400)->setJSON([ @@ -416,19 +473,17 @@ class AppContentManagementController extends AdminController ] ], 'question' => [ - 'rules' => 'required|max_length[1000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', + 'rules' => 'required|max_length[1000]', 'errors' => [ 'required' => 'Question is required', 'max_length' => 'Question cannot exceed 1000 characters', - 'regex_match' => 'Question contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ], 'answer' => [ - 'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]', + 'rules' => 'required|max_length[5000]', 'errors' => [ 'required' => 'Answer is required', 'max_length' => 'Answer cannot exceed 5000 characters', - 'regex_match' => 'Answer contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed' ] ] ]; @@ -443,9 +498,50 @@ class AppContentManagementController extends AdminController ]); } - $request_post_data = $this->request->getPost(); - $sanitized_post_data = sanitizeInputArrayAdvanced($request_post_data); - $data = array_filter($sanitized_post_data, fn($v) => $v !== '' && $v !== null); + /************************************************************************** + * XSS PROTECTION FOR 'question' and 'answer' + ************************************************************************** + * + * Applying the same security model as `frontend_content`. + * + * 1. Validate raw `question` and `answer` for malicious tags using `hasXssTags()`. + * If found, reject the request immediately. + * 2. Sanitize all *other* fields using the generic `sanitizeInputArrayAdvanced`. + * 3. Sanitize the `question` and `answer` using the HTML-aware `sanitizeHtml()` + * function to allow safe tags before saving. + * + *************************************************************************/ + + // Step 1: Validate raw input for XSS threats. + $rawQuestion = $this->request->getPost('question'); + $rawAnswer = $this->request->getPost('answer'); + $xssErrors = []; + + if ($this->hasXssTags($rawQuestion)) { + $xssErrors['question'] = 'Question contains restricted tags. Script, iframe and event handlers are not allowed'; + } + if ($this->hasXssTags($rawAnswer)) { + $xssErrors['answer'] = 'Answer contains restricted tags. Script, iframe and event handlers are not allowed'; + } + + if (!empty($xssErrors)) { + return $this->response->setStatusCode(400)->setJSON([ + 'status' => 'error', + 'message' => 'Input validation failed', + 'code' => 400, + 'errors' => $xssErrors, + 'ref' => $ref + ]); + } + + // Step 2 & 3: Sanitize and combine data. + $otherPostData = $this->request->getPost(); + unset($otherPostData['question'], $otherPostData['answer']); + $data = sanitizeInputArrayAdvanced($otherPostData); + + $data['question'] = $this->sanitizeHtml($rawQuestion); + $data['answer'] = $this->sanitizeHtml($rawAnswer); + $id = $data['faq_id'] ?? null; if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) { @@ -470,12 +566,6 @@ class AppContentManagementController extends AdminController $msg = "Updated"; } - // if ($returnType === 'web') { - // return redirect()->back()->with($status ? 'success' : 'error', "FAQ $msg " . ($status ? 'successfully' : 'failed')); - // } - - // return $this->response->setJSON([ - // ])->setStatusCode($result ? 200 : 400); return $this->response->setJSON([ 'status' => $status ? 'success' : 'error', 'message' => "FAQ $msg " . ($status ? 'successfully' : 'failed'), @@ -611,4 +701,104 @@ class AppContentManagementController extends AdminController // } + + // Add these two private methods inside AppContentManagementController + + + /** + * ================================================================================= + * HTML SANITIZATION & VALIDATION HELPER METHODS + * ================================================================================= + * The following two methods are the core of the XSS protection logic. + */ + + + /** + * sanitizeHtml() + * + * This function cleans a string of HTML, ensuring it is safe to display in a browser. + * It allows a specific set of safe HTML tags and removes any dangerous attributes + * from those tags. + * + * @param string $input The raw HTML string from user input. + * @return string The cleaned, safe HTML string. + */ + private function sanitizeHtml(string $input): string + { + /** + * Define a whitelist of allowed HTML tags. Any tag not in this list will be + * completely removed. We are allowing basic formatting, lists, tables, etc. + */ + // ✅ Added , ,

-

,
,
, , 
for Jodit support + $allowed_tags = '