mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-04-17 05:19:43 -04:00
* Fix business logic vulnerability allowing negative sale totals (GHSA-wv3j-pp8r-7q43) Add server-side validation in postEditItem() to reject negative prices, quantities, and discounts, as well as percentage discounts exceeding 100% and fixed discounts exceeding the item total. Also block sale completion with negative totals in non-return mode to prevent fraud/theft. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix: exempt return mode from negative quantity validation Return mode legitimately stores items with negative quantities. The quantity validation now skips the non-negative check in return mode, consistent with the existing return mode exemption in postComplete(). Also use abs() for fixed discount comparison to handle return quantities. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Refactor: use $rules + validate() pattern per review feedback Address review comments from jekkos on PR #4450: 1. Use CI4 $rules variable with custom non_negative_decimal validation rule instead of manual if-checks for price/discount validation. 2. Add validation error strings to all 44 non-English language files (English fallback values used until translations are contributed). 3. Use validate() method with $messages array for localized error display, maintaining the existing controller pattern. Additional improvements: - Add non_negative_decimal rule to OSPOSRules.php (leverages parse_decimals() for locale-aware decimal parsing) - Preserve manual checks for business logic (return mode quantity exemption, discount bounds via bccomp) - Fix PHP 8.1+ compatibility: avoid passing method return to reset() - Explicit empty discount handling for bc-math safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix: rename to nonNegativeDecimal (PSR), clear non-English translation strings - Rename validation rule method non_negative_decimal → nonNegativeDecimal in OSPOSRules.php and all $rules/$messages references in Sales.php (PSR naming per @objecttothis review) - Replace English fallback text with "" in 43 non-English language files so CI4 falls back to the base language string; weblate will handle translations (per @jekkos and @objecttothis agreement) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Paul <morimori-dev@github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
154 lines
4.6 KiB
PHP
154 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Config\Validation;
|
|
|
|
use App\Models\Employee;
|
|
use CodeIgniter\HTTP\IncomingRequest;
|
|
use Config\OSPOS;
|
|
use Config\Services;
|
|
|
|
/**
|
|
* @property Employee employee
|
|
* @property IncomingRequest request
|
|
*/
|
|
class OSPOSRules
|
|
{
|
|
private IncomingRequest $request;
|
|
private array $config;
|
|
|
|
/**
|
|
* Validates the username and password sent to the login view. User is logged in on successful validation.
|
|
*
|
|
* @param string $username Username to check against.
|
|
* @param string $fields Comma separated string of the fields for validation.
|
|
* @param array $data Data sent to the view.
|
|
* @param string|null $error The error sent back to the validation handler on failure.
|
|
* @return bool True if validation passes or false if there are errors.
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function login_check(string $username, string $fields, array $data, ?string &$error = null): bool
|
|
{
|
|
$employee = model(Employee::class);
|
|
$this->request = Services::request();
|
|
$this->config = config(OSPOS::class)->settings;
|
|
|
|
// Installation Check
|
|
if (!$this->installation_check()) {
|
|
$error = lang('Login.invalid_installation');
|
|
|
|
return false;
|
|
}
|
|
|
|
$password = $data['password'];
|
|
if (!$employee->login($username, $password)) {
|
|
$error = lang('Login.invalid_username_and_password');
|
|
|
|
return false;
|
|
}
|
|
|
|
$gcaptcha_enabled = array_key_exists('gcaptcha_enable', $this->config) && $this->config['gcaptcha_enable'];
|
|
if ($gcaptcha_enabled) {
|
|
$g_recaptcha_response = $this->request->getPost('g-recaptcha-response');
|
|
|
|
if (!$this->gcaptcha_check($g_recaptcha_response)) {
|
|
$error = lang('Login.invalid_gcaptcha');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if GCaptcha verification was successful.
|
|
*
|
|
* @param $response
|
|
* @return bool true on successful GCaptcha verification or false if GCaptcha failed.
|
|
*/
|
|
private function gcaptcha_check($response): bool
|
|
{
|
|
if (!empty($response)) {
|
|
$check = [
|
|
'secret' => $this->config['gcaptcha_secret_key'],
|
|
'response' => $response,
|
|
'remoteip' => $this->request->getIPAddress()
|
|
];
|
|
|
|
$ch = curl_init();
|
|
|
|
curl_setopt($ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($check));
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
$result = curl_exec($ch);
|
|
|
|
curl_close($ch);
|
|
|
|
$status = json_decode($result, true);
|
|
|
|
if (!empty($status['success'])) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks to make sure dependency PHP extensions are installed
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function installation_check(): bool
|
|
{
|
|
$installed_extensions = implode(', ', get_loaded_extensions());
|
|
$required_extensions = ['bcmath', 'intl', 'gd', 'openssl', 'mbstring', 'curl', 'xml', 'json'];
|
|
$pattern = '/';
|
|
|
|
foreach ($required_extensions as $extension) {
|
|
$pattern .= '(?=.*\b' . preg_quote($extension, '/') . '\b)';
|
|
}
|
|
|
|
$pattern .= '/i';
|
|
$is_installed = preg_match($pattern, $installed_extensions);
|
|
|
|
if (!$is_installed) {
|
|
log_message('error', '[ERROR] Check your php.ini.');
|
|
log_message('error', "PHP installed extensions: $installed_extensions");
|
|
log_message('error', 'PHP required extensions: ' . implode(', ', $required_extensions));
|
|
}
|
|
|
|
return $is_installed;
|
|
}
|
|
|
|
/**
|
|
* Validates the candidate as a decimal number. Takes the locale into account. Used in validation rule calls.
|
|
*
|
|
* @param string $candidate
|
|
* @param string|null $error
|
|
* @return bool
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function decimal_locale(string $candidate, ?string &$error = null): bool
|
|
{
|
|
return parse_decimals($candidate) !== false;
|
|
}
|
|
|
|
/**
|
|
* Validates that a locale-aware decimal value is non-negative (>= 0).
|
|
*
|
|
* @param string $candidate
|
|
* @param string|null $error
|
|
* @return bool
|
|
* @noinspection PhpUnused
|
|
*/
|
|
public function nonNegativeDecimal(string $candidate, ?string &$error = null): bool
|
|
{
|
|
$value = parse_decimals($candidate);
|
|
|
|
return $value !== false && $value >= 0;
|
|
}
|
|
}
|