diff --git a/app/code/Magento/Contact/Model/Mail.php b/app/code/Magento/Contact/Model/Mail.php index 43c1974252b5a..7735f5d7dd818 100644 --- a/app/code/Magento/Contact/Model/Mail.php +++ b/app/code/Magento/Contact/Model/Mail.php @@ -10,6 +10,9 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Area; +use Magento\Framework\Validator\GlobalForbiddenPatterns; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\LocalizedException; class Mail implements MailInterface { @@ -33,6 +36,11 @@ class Mail implements MailInterface */ private $storeManager; + /** + * @var GlobalForbiddenPatterns + */ + private $forbiddenPatternsValidator; + /** * Initialize dependencies. * @@ -40,17 +48,20 @@ class Mail implements MailInterface * @param TransportBuilder $transportBuilder * @param StateInterface $inlineTranslation * @param StoreManagerInterface|null $storeManager + * @param GlobalForbiddenPatterns $forbiddenPatternsValidator */ public function __construct( ConfigInterface $contactsConfig, TransportBuilder $transportBuilder, StateInterface $inlineTranslation, - StoreManagerInterface $storeManager = null + StoreManagerInterface $storeManager = null, + GlobalForbiddenPatterns $forbiddenPatternsValidator ) { $this->contactsConfig = $contactsConfig; $this->transportBuilder = $transportBuilder; $this->inlineTranslation = $inlineTranslation; $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); + $this->forbiddenPatternsValidator = $forbiddenPatternsValidator; } /** @@ -59,9 +70,24 @@ public function __construct( * @param string $replyTo * @param array $variables * @return void + * @throws LocalizedException */ public function send($replyTo, array $variables) { + $validationErrors = []; + $fieldsToValidate = [ + 'name' => $variables['data']['name'] ?? '', + 'comment' => $variables['data']['comment'] ?? '', + 'email' => $variables['data']['email'] ?? '', + ]; + $this->forbiddenPatternsValidator->validateData($fieldsToValidate, $validationErrors); + + if (!empty($validationErrors)) { + throw new \Magento\Framework\Exception\LocalizedException( + __(implode("\n", $validationErrors)) + ); + } + /** @see \Magento\Contact\Controller\Index\Post::validatedParams() */ $replyToName = !empty($variables['data']['name']) ? $variables['data']['name'] : null; diff --git a/app/code/Magento/Customer/Model/Validator/City.php b/app/code/Magento/Customer/Model/Validator/City.php index 0b53551dfd88f..6ff8665f32a83 100644 --- a/app/code/Magento/Customer/Model/Validator/City.php +++ b/app/code/Magento/Customer/Model/Validator/City.php @@ -9,6 +9,7 @@ use Magento\Customer\Model\Customer; use Magento\Framework\Validator\AbstractValidator; +use Magento\Framework\Validator\GlobalCityValidator; /** * Customer city fields validator. @@ -16,46 +17,34 @@ class City extends AbstractValidator { /** - * Allowed characters: - * - * \p{L}: Unicode letters. - * \p{M}: Unicode marks (diacritic marks, accents, etc.). - * ': Apostrophe mark. - * \s: Whitespace characters (spaces, tabs, newlines, etc.). + * @var GlobalCityValidator */ - private const PATTERN_CITY = '/(?:[\p{L}\p{M}\s\-\']{1,100})/u'; + private $cityValidator; /** - * Validate city fields. + * City constructor. * - * @param Customer $customer - * @return bool + * @param GlobalCityValidator $cityValidator */ - public function isValid($customer) + public function __construct(GlobalCityValidator $cityValidator) { - if (!$this->isValidCity($customer->getCity())) { - parent::_addMessages([[ - 'city' => "Invalid City. Please use A-Z, a-z, 0-9, -, ', spaces" - ]]); - } - - return count($this->_messages) == 0; + $this->cityValidator = $cityValidator; } /** - * Check if city field is valid. + * Validate city fields. * - * @param string|null $cityValue + * @param Customer $customer * @return bool */ - private function isValidCity($cityValue) + public function isValid($customer): bool { - if ($cityValue != null) { - if (preg_match(self::PATTERN_CITY, $cityValue, $matches)) { - return $matches[0] == $cityValue; - } + if (!$this->cityValidator->isValidCity($customer->getCity())) { + parent::_addMessages([[ + 'city' => __("Invalid City. Please use only A-Z, a-z, 0-9, spaces, commas, -, ., ', &, [], ().") + ]]); } - return true; + return count($this->_messages) == 0; } } diff --git a/app/code/Magento/Customer/Model/Validator/Name.php b/app/code/Magento/Customer/Model/Validator/Name.php index 75d460358970c..3a2f05bad4989 100644 --- a/app/code/Magento/Customer/Model/Validator/Name.php +++ b/app/code/Magento/Customer/Model/Validator/Name.php @@ -9,13 +9,27 @@ use Magento\Customer\Model\Customer; use Magento\Framework\Validator\AbstractValidator; +use Magento\Framework\Validator\GlobalNameValidator; /** * Customer name fields validator. */ class Name extends AbstractValidator { - private const PATTERN_NAME = '/(?:[\p{L}\p{M}\,\-\_\.\'’`&\s\d]){1,255}+/u'; + /** + * @var GlobalNameValidator + */ + private $nameValidator; + + /** + * Name constructor. + * + * @param GlobalNameValidator $nameValidator + */ + public function __construct(GlobalNameValidator $nameValidator) + { + $this->nameValidator = $nameValidator; + } /** * Validate name fields. @@ -25,35 +39,18 @@ class Name extends AbstractValidator */ public function isValid($customer) { - if (!$this->isValidName($customer->getFirstname())) { - parent::_addMessages([['firstname' => 'First Name is not valid!']]); + if (!$this->nameValidator->isValidName($customer->getFirstname())) { + parent::_addMessages([['firstname' => __('First Name is not valid!')]]); } - if (!$this->isValidName($customer->getLastname())) { - parent::_addMessages([['lastname' => 'Last Name is not valid!']]); + if (!$this->nameValidator->isValidName($customer->getLastname())) { + parent::_addMessages([['lastname' => __('Last Name is not valid!')]]); } - if (!$this->isValidName($customer->getMiddlename())) { - parent::_addMessages([['middlename' => 'Middle Name is not valid!']]); + if (!$this->nameValidator->isValidName($customer->getMiddlename())) { + parent::_addMessages([['middlename' => __('Middle Name is not valid!')]]); } return count($this->_messages) == 0; } - - /** - * Check if name field is valid. - * - * @param string|null $nameValue - * @return bool - */ - private function isValidName($nameValue) - { - if ($nameValue != null) { - if (preg_match(self::PATTERN_NAME, $nameValue, $matches)) { - return $matches[0] == $nameValue; - } - } - - return true; - } } diff --git a/app/code/Magento/Customer/Model/Validator/Street.php b/app/code/Magento/Customer/Model/Validator/Street.php index 7de57d0ed32ef..96e5d92e667f7 100644 --- a/app/code/Magento/Customer/Model/Validator/Street.php +++ b/app/code/Magento/Customer/Model/Validator/Street.php @@ -9,6 +9,7 @@ use Magento\Customer\Model\Customer; use Magento\Framework\Validator\AbstractValidator; +use Magento\Framework\Validator\GlobalStreetValidator; /** * Customer street fields validator. @@ -16,19 +17,19 @@ class Street extends AbstractValidator { /** - * Allowed characters: + * @var GlobalStreetValidator + */ + private $streetValidator; + + /** + * Street constructor. * - * \p{L}: Unicode letters. - * \p{M}: Unicode marks (diacritic marks, accents, etc.). - * ,: Comma. - * -: Hyphen. - * .: Period. - * `'’: Single quotes, both regular and right single quotation marks. - * &: Ampersand. - * \s: Whitespace characters (spaces, tabs, newlines, etc.). - * \d: Digits (0-9). + * @param GlobalStreetValidator $streetValidator */ - private const PATTERN_STREET = "/(?:[\p{L}\p{M}\"[],-.'’`&\s\d]){1,255}+/u"; + public function __construct(GlobalStreetValidator $streetValidator) + { + $this->streetValidator = $streetValidator; + } /** * Validate street fields. @@ -36,33 +37,19 @@ class Street extends AbstractValidator * @param Customer $customer * @return bool */ - public function isValid($customer) + public function isValid($customer): bool { foreach ($customer->getStreet() as $street) { - if (!$this->isValidStreet($street)) { + if (!$this->streetValidator->isValidStreet($street)) { parent::_addMessages([[ - 'street' => "Invalid Street Address. Please use A-Z, a-z, 0-9, , - . ' ’ ` & spaces" + 'street' => __( + "Invalid Street Address. Please use only A-Z, a-z, 0-9, spaces, commas, -, ., ', " . + "&, [], ()" + ) ]]); } } return count($this->_messages) == 0; } - - /** - * Check if street field is valid. - * - * @param string|null $streetValue - * @return bool - */ - private function isValidStreet($streetValue) - { - if ($streetValue != null) { - if (preg_match(self::PATTERN_STREET, $streetValue, $matches)) { - return $matches[0] == $streetValue; - } - } - - return true; - } } diff --git a/app/code/Magento/Customer/Model/Validator/Telephone.php b/app/code/Magento/Customer/Model/Validator/Telephone.php index 0c85cb51f7e3d..668d89fc26445 100644 --- a/app/code/Magento/Customer/Model/Validator/Telephone.php +++ b/app/code/Magento/Customer/Model/Validator/Telephone.php @@ -9,6 +9,7 @@ use Magento\Customer\Model\Customer; use Magento\Framework\Validator\AbstractValidator; +use Magento\Framework\Validator\GlobalPhoneValidation; /** * Customer telephone fields validator. @@ -16,15 +17,20 @@ class Telephone extends AbstractValidator { /** - * Allowed char: + * @var GlobalPhoneValidation + */ + private $phoneValidator; + + /** + * Telephone constructor. * - * \() :Matches open and close parentheses - * \+: Matches the plus sign. - * \-: Matches the hyphen. - * \d: Digits (0-9). + * @param GlobalPhoneValidation $phoneValidator */ - private const PATTERN_TELEPHONE = '/(?:[\d\s\+\-\()]{1,20})/u'; - + public function __construct(GlobalPhoneValidation $phoneValidator) + { + $this->phoneValidator = $phoneValidator; + } + /** * Validate telephone fields. * @@ -33,29 +39,12 @@ class Telephone extends AbstractValidator */ public function isValid($customer) { - if (!$this->isValidTelephone($customer->getTelephone())) { + if (!$this->phoneValidator->isValidPhone($customer->getTelephone())) { parent::_addMessages([[ - 'telephone' => "Invalid Phone Number. Please use 0-9, +, -, (, ) and space." + 'telephone' => __('Invalid Phone Number. Please use 0-9, +, -, (), /, and space.') ]]); } return count($this->_messages) == 0; } - - /** - * Check if telephone field is valid. - * - * @param string|null $telephoneValue - * @return bool - */ - private function isValidTelephone($telephoneValue) - { - if ($telephoneValue != null) { - if (preg_match(self::PATTERN_TELEPHONE, (string) $telephoneValue, $matches)) { - return $matches[0] == $telephoneValue; - } - } - - return true; - } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Validator/CityTest.php b/app/code/Magento/Customer/Test/Unit/Model/Validator/CityTest.php index 9c15427154fea..01cda4b49fab3 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Validator/CityTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/CityTest.php @@ -9,6 +9,7 @@ use Magento\Customer\Model\Validator\City; use Magento\Customer\Model\Customer; +use Magento\Framework\Validator\GlobalCityValidator; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -20,19 +21,25 @@ class CityTest extends TestCase /** * @var City */ - private City $nameValidator; + private City $cityValidator; /** * @var Customer|MockObject */ private MockObject $customerMock; + /** + * @var GlobalCityValidator|MockObject + */ + private MockObject $globalCityValidatorMock; + /** * @return void */ protected function setUp(): void { - $this->nameValidator = new City; + $this->globalCityValidatorMock = $this->createMock(GlobalCityValidator::class); + $this->cityValidator = new City($this->globalCityValidatorMock); $this->customerMock = $this ->getMockBuilder(Customer::class) ->disableOriginalConstructor() @@ -41,45 +48,66 @@ protected function setUp(): void } /** - * Test for allowed apostrophe and other punctuation characters in customer names + * Test for allowed punctuation characters in city names * * @param string $city * @param string $message * @return void - * @dataProvider expectedPunctuationInNamesDataProvider + * @dataProvider expectedPunctuationInCityDataProvider */ - public function testValidateCorrectPunctuationInNames( + public function testValidateCorrectPunctuationInCity( string $city, string $message ) { $this->customerMock->expects($this->once())->method('getCity')->willReturn($city); - $isValid = $this->nameValidator->isValid($this->customerMock); + $this->globalCityValidatorMock->expects($this->once()) + ->method('isValidCity') + ->with($city) + ->willReturn(true); + + $isValid = $this->cityValidator->isValid($this->customerMock); $this->assertTrue($isValid, $message); } /** * @return array */ - public function expectedPunctuationInNamesDataProvider(): array + public function expectedPunctuationInCityDataProvider(): array { return [ + [ + 'city' => 'New York', + 'message' => 'Spaces must be allowed in city names' + ], + [ + 'city' => 'São Paulo', + 'message' => 'Accented characters and spaces must be allowed in city names' + ], + [ + 'city' => 'St. Louis', + 'message' => 'Periods and spaces must be allowed in city names' + ], [ 'city' => 'Москва', - 'message' => 'Unicode letters must be allowed in city' + 'message' => 'Unicode letters must be allowed in city names' ], [ - 'city' => 'Мо́сква', - 'message' => 'Unicode marks must be allowed in city' + 'city' => 'Moscow \'', + 'message' => 'Apostrophe characters must be allowed in city names' ], [ - 'city' => ' Moscow \'', - 'message' => 'Apostrophe characters must be allowed in city' + 'city' => 'St.-Pierre', + 'message' => 'Hyphens must be allowed in city names' ], [ - 'city' => ' Moscow Moscow', - 'message' => 'Whitespace characters must be allowed in city' - ] + 'city' => 'Offenbach (Main)', + 'message' => 'Parentheses must be allowed in city names' + ], + [ + 'city' => 'Rome: The Eternal City', + 'message' => 'Colons must be allowed in city names' + ], ]; } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Validator/NameTest.php b/app/code/Magento/Customer/Test/Unit/Model/Validator/NameTest.php index 5033774d54494..c5f953ff44fa7 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Validator/NameTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/NameTest.php @@ -8,6 +8,7 @@ namespace Magento\Customer\Test\Unit\Model\Validator; use Magento\Customer\Model\Validator\Name; +use Magento\Framework\Validator\GlobalNameValidator; use Magento\Customer\Model\Customer; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -22,6 +23,11 @@ class NameTest extends TestCase */ private Name $nameValidator; + /** + * @var GlobalNameValidator|MockObject + */ + private MockObject $globalNameValidatorMock; + /** * @var Customer|MockObject */ @@ -32,7 +38,8 @@ class NameTest extends TestCase */ protected function setUp(): void { - $this->nameValidator = new Name; + $this->globalNameValidatorMock = $this->createMock(GlobalNameValidator::class); + $this->nameValidator = new Name($this->globalNameValidatorMock); $this->customerMock = $this ->getMockBuilder(Customer::class) ->disableOriginalConstructor() @@ -60,6 +67,15 @@ public function testValidateCorrectPunctuationInNames( $this->customerMock->expects($this->once())->method('getMiddlename')->willReturn($middleName); $this->customerMock->expects($this->once())->method('getLastname')->willReturn($lastName); + // Mock the GlobalNameValidator behavior + $this->globalNameValidatorMock->expects($this->exactly(3)) + ->method('isValidName') + ->willReturnMap([ + [$firstName, true], + [$middleName, true], + [$lastName, true], + ]); + $isValid = $this->nameValidator->isValid($this->customerMock); $this->assertTrue($isValid, $message); } @@ -73,25 +89,25 @@ public function expectedPunctuationInNamesDataProvider(): array [ 'firstName' => 'John', 'middleName' => '', - 'lastNameName' => 'O’Doe', + 'lastName' => 'O’Doe', 'message' => 'Inclined apostrophe must be allowed in names (iOS Smart Punctuation compatibility)' ], [ 'firstName' => 'John', 'middleName' => '', - 'lastNameName' => 'O\'Doe', + 'lastName' => 'O\'Doe', 'message' => 'Legacy straight apostrophe must be allowed in names' ], [ 'firstName' => 'John', 'middleName' => '', - 'lastNameName' => 'O`Doe', + 'lastName' => 'O`Doe', 'message' => 'Grave accent back quote character must be allowed in names' ], [ 'firstName' => 'John & Smith', 'middleName' => '', - 'lastNameName' => 'O`Doe', + 'lastName' => 'O`Doe', 'message' => 'Special character ampersand(&) must be allowed in names' ] ]; diff --git a/app/code/Magento/Customer/Test/Unit/Model/Validator/StreetTest.php b/app/code/Magento/Customer/Test/Unit/Model/Validator/StreetTest.php index 6d40bec460b3e..9a7a91bd750ca 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Validator/StreetTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/StreetTest.php @@ -8,6 +8,7 @@ namespace Magento\Customer\Test\Unit\Model\Validator; use Magento\Customer\Model\Validator\Street; +use Magento\Framework\Validator\GlobalStreetValidator; use Magento\Customer\Model\Customer; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -20,7 +21,12 @@ class StreetTest extends TestCase /** * @var Street */ - private Street $nameValidator; + private Street $streetValidator; + + /** + * @var GlobalStreetValidator|MockObject + */ + private MockObject $globalStreetValidatorMock; /** * @var Customer|MockObject @@ -32,7 +38,8 @@ class StreetTest extends TestCase */ protected function setUp(): void { - $this->nameValidator = new Street; + $this->globalStreetValidatorMock = $this->createMock(GlobalStreetValidator::class); + $this->streetValidator = new Street($this->globalStreetValidatorMock); $this->customerMock = $this ->getMockBuilder(Customer::class) ->disableOriginalConstructor() @@ -41,27 +48,34 @@ protected function setUp(): void } /** - * Test for allowed apostrophe and other punctuation characters in customer names + * Test for allowed characters in street addresses * * @param array $street * @param string $message * @return void - * @dataProvider expectedPunctuationInNamesDataProvider + * @dataProvider expectedPunctuationInStreetDataProvider */ - public function testValidateCorrectPunctuationInNames( + public function testValidateCorrectPunctuationInStreet( array $street, string $message - ) { + ): void { $this->customerMock->expects($this->once())->method('getStreet')->willReturn($street); - $isValid = $this->nameValidator->isValid($this->customerMock); + // Mock the GlobalStreetValidator behavior + $this->globalStreetValidatorMock->expects($this->exactly(count($street))) + ->method('isValidStreet') + ->willReturn(true); + + $isValid = $this->streetValidator->isValid($this->customerMock); $this->assertTrue($isValid, $message); } /** + * Data provider for valid street names + * * @return array */ - public function expectedPunctuationInNamesDataProvider(): array + public function expectedPunctuationInStreetDataProvider(): array { return [ [ @@ -102,7 +116,7 @@ public function expectedPunctuationInNamesDataProvider(): array 'O`Connell Street', '321 Birch Boulevard ’Willow Retreat’' ], - 'message' => 'quotes must be allowed in street' + 'message' => 'Quotes must be allowed in street' ], [ 'street' => [ @@ -127,6 +141,14 @@ public function expectedPunctuationInNamesDataProvider(): array '876 Elm Way' ], 'message' => 'Digits must be allowed in street' + ], + [ + 'street' => [ + '1234 Elm St. [Apartment 5]', + 'Main St. (Suite 200)', + '456 Pine St. [Unit 10]' + ], + 'message' => 'Square brackets and parentheses must be allowed in street' ] ]; } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Validator/TelephoneTest.php b/app/code/Magento/Customer/Test/Unit/Model/Validator/TelephoneTest.php index 47a9d6da18831..75388d3ac5484 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Validator/TelephoneTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/TelephoneTest.php @@ -8,6 +8,7 @@ namespace Magento\Customer\Test\Unit\Model\Validator; use Magento\Customer\Model\Validator\Telephone; +use Magento\Framework\Validator\GlobalPhoneValidation; use Magento\Customer\Model\Customer; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -20,7 +21,12 @@ class TelephoneTest extends TestCase /** * @var Telephone */ - private Telephone $nameValidator; + private Telephone $telephoneValidator; + + /** + * @var GlobalPhoneValidation|MockObject + */ + private MockObject $globalPhoneValidationMock; /** * @var Customer|MockObject @@ -28,58 +34,76 @@ class TelephoneTest extends TestCase private MockObject $customerMock; /** + * Set up the test environment. + * * @return void */ protected function setUp(): void { - $this->nameValidator = new Telephone; - $this->customerMock = $this - ->getMockBuilder(Customer::class) + $this->globalPhoneValidationMock = $this->createMock(GlobalPhoneValidation::class); + $this->telephoneValidator = new Telephone($this->globalPhoneValidationMock); + $this->customerMock = $this->getMockBuilder(Customer::class) ->disableOriginalConstructor() ->addMethods(['getTelephone']) ->getMock(); } /** - * Test for allowed apostrophe and other punctuation characters in customer names + * Test for allowed characters in customer telephone numbers. * * @param string $telephone * @param string $message * @return void - * @dataProvider expectedPunctuationInNamesDataProvider + * @dataProvider expectedPunctuationInTelephoneDataProvider */ - public function testValidateCorrectPunctuationInNames( + public function testValidateCorrectPunctuationInTelephone( string $telephone, string $message ) { $this->customerMock->expects($this->once())->method('getTelephone')->willReturn($telephone); - $isValid = $this->nameValidator->isValid($this->customerMock); + // Mock the GlobalPhoneValidation behavior + $this->globalPhoneValidationMock->expects($this->once()) + ->method('isValidPhone') + ->with($telephone) + ->willReturn(true); + + $isValid = $this->telephoneValidator->isValid($this->customerMock); $this->assertTrue($isValid, $message); } /** + * Data provider for testValidateCorrectPunctuationInTelephone. + * * @return array */ - public function expectedPunctuationInNamesDataProvider(): array + public function expectedPunctuationInTelephoneDataProvider(): array { return [ [ 'telephone' => '(1)99887766', - 'message' => 'parentheses must be allowed in telephone' + 'message' => 'Parentheses must be allowed in telephone numbers.' ], [ 'telephone' => '+6255554444', - 'message' => 'plus sign be allowed in telephone' + 'message' => 'Plus sign must be allowed in telephone numbers.' ], [ 'telephone' => '555-555-555', - 'message' => 'hyphen must be allowed in telephone' + 'message' => 'Hyphen must be allowed in telephone numbers.' ], [ 'telephone' => '123456789', - 'message' => 'Digits (numbers) must be allowed in telephone' - ] + 'message' => 'Digits (numbers) must be allowed in telephone numbers.' + ], + [ + 'telephone' => '123 456 789', + 'message' => 'Spaces must be allowed in telephone numbers.' + ], + [ + 'telephone' => '123/456/789', + 'message' => 'Forward slashes must be allowed in telephone numbers.' + ], ]; } } diff --git a/app/code/Magento/Quote/Model/ValidationRules/AddressValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/AddressValidationRule.php new file mode 100644 index 0000000000000..453ee82e354a5 --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/AddressValidationRule.php @@ -0,0 +1,109 @@ +forbiddenPatternsValidator = $forbiddenPatternsValidator; + $this->nameValidator = $nameValidator; + $this->cityValidator = $cityValidator; + $this->phoneValidator = $phoneValidator; + $this->streetValidator = $streetValidator; + } + + /** + * Validates the address fields and applies forbidden pattern checks + * + * @param mixed $address The address object to validate. + * @param array &$validationErrors An array to store validation errors. + * @return void + */ + public function validateAddress($address, array &$validationErrors): void + { + // Define the fields to validate with their respective validators + $fieldsToValidate = [ + 'First Name' => [$address->getFirstname(), 'isValidName', $this->nameValidator], + 'Middle Name' => [$address->getMiddlename(), 'isValidName', $this->nameValidator], + 'Last Name' => [$address->getLastname(), 'isValidName', $this->nameValidator], + 'Prefix' => [$address->getPrefix(), 'isValidName', $this->nameValidator], + 'Suffix' => [$address->getSuffix(), 'isValidName', $this->nameValidator], + 'City' => [$address->getCity(), 'isValidCity', $this->cityValidator], + 'Telephone' => [$address->getTelephone(), 'isValidPhone', $this->phoneValidator], + 'Fax' => [$address->getFax(), 'isValidPhone', $this->phoneValidator], + 'Street' => [$address->getStreet(), 'isValidStreet', $this->streetValidator], + ]; + + // Validate each field + foreach ($fieldsToValidate as $fieldName => [$fieldValue, $validationMethod, $validatorInstance]) { + if (is_array($fieldValue)) { + foreach ($fieldValue as $value) { + if (!$validatorInstance->$validationMethod($value)) { + error_log("Invalid value: " . $fieldValue); + $validationErrors[] = __("$fieldName is not valid"); + } + } + } else { + if (!$validatorInstance->$validationMethod($fieldValue)) { + error_log("Invalid value: " . $fieldValue); + $validationErrors[] = __("$fieldName is not valid"); + } + } + } + + if (empty($validationErrors)) { + $this->forbiddenPatternsValidator->validateData($address->getData(), $validationErrors); + } + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/BillingAddressValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/BillingAddressValidationRule.php index 465aebdc418ed..4adc857469481 100644 --- a/app/code/Magento/Quote/Model/ValidationRules/BillingAddressValidationRule.php +++ b/app/code/Magento/Quote/Model/ValidationRules/BillingAddressValidationRule.php @@ -9,6 +9,7 @@ use Magento\Framework\Validation\ValidationResultFactory; use Magento\Quote\Model\Quote; +use Magento\Quote\Model\ValidationRules\AddressValidationRule; /** * @inheritdoc @@ -26,14 +27,24 @@ class BillingAddressValidationRule implements QuoteValidationRuleInterface private $validationResultFactory; /** + * @var AddressValidationRule + */ + private $addressValidationRule; + + /** + * Constructor. + * * @param ValidationResultFactory $validationResultFactory + * @param AddressValidationRule $addressValidationRule * @param string $generalMessage */ public function __construct( ValidationResultFactory $validationResultFactory, + AddressValidationRule $addressValidationRule, string $generalMessage = '' ) { $this->validationResultFactory = $validationResultFactory; + $this->addressValidationRule = $addressValidationRule; $this->generalMessage = $generalMessage; } @@ -43,8 +54,10 @@ public function __construct( public function validate(Quote $quote): array { $validationErrors = []; + $billingAddress = $quote->getBillingAddress(); $billingAddress->setStoreId($quote->getStoreId()); + $validationResult = $billingAddress->validate(); if ($validationResult !== true) { $validationErrors = [__($this->generalMessage)]; @@ -53,6 +66,10 @@ public function validate(Quote $quote): array $validationErrors = array_merge($validationErrors, $validationResult); } + if (empty($validationErrors)) { + $this->addressValidationRule->validateAddress($billingAddress, $validationErrors); + } + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; } } diff --git a/app/code/Magento/Quote/Model/ValidationRules/NameValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/NameValidationRule.php new file mode 100644 index 0000000000000..792d556346d03 --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/NameValidationRule.php @@ -0,0 +1,86 @@ +validationResultFactory = $validationResultFactory; + $this->nameValidator = $nameValidator; + $this->forbiddenPatternsValidator = $forbiddenPatternsValidator; + } + + /** + * Validate the first name, middle name, last name, prefix, and suffix in the quote. + * + * @param Quote $quote + * @return array + */ + public function validate(Quote $quote): array + { + $validationErrors = []; + + // Define the fields to validate with their respective validators + $fieldsToValidate = [ + 'First Name' => [$quote->getCustomerFirstname(), 'isValidName', $this->nameValidator], + 'Middle Name' => [$quote->getCustomerMiddlename(), 'isValidName', $this->nameValidator], + 'Last Name' => [$quote->getCustomerLastname(), 'isValidName', $this->nameValidator], + 'Prefix' => [$quote->getCustomerPrefix(), 'isValidName', $this->nameValidator], + 'Suffix' => [$quote->getCustomerSuffix(), 'isValidName', $this->nameValidator], + ]; + + // Validate each field + foreach ($fieldsToValidate as $fieldName => [$fieldValue, $validationMethod, $validatorInstance]) { + if (!$validatorInstance->$validationMethod($fieldValue)) { + $validationErrors[] = __("$fieldName is not valid"); + } + } + + // Perform regex validation only if no other errors exist + if (empty($validationErrors)) { + $this->forbiddenPatternsValidator->validateData($quote->getData(), $validationErrors); + } + + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/ShippingAddressValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/ShippingAddressValidationRule.php index 2f215c17e6d73..0ee0f987b3790 100644 --- a/app/code/Magento/Quote/Model/ValidationRules/ShippingAddressValidationRule.php +++ b/app/code/Magento/Quote/Model/ValidationRules/ShippingAddressValidationRule.php @@ -9,6 +9,7 @@ use Magento\Framework\Validation\ValidationResultFactory; use Magento\Quote\Model\Quote; +use Magento\Quote\Model\ValidationRules\AddressValidationRule; /** * @inheritdoc @@ -25,15 +26,23 @@ class ShippingAddressValidationRule implements QuoteValidationRuleInterface */ private $validationResultFactory; + /** + * @var AddressValidationRule + */ + private $addressValidationRule; + /** * @param ValidationResultFactory $validationResultFactory + * @param AddressValidationRule $addressValidationRule * @param string $generalMessage */ public function __construct( ValidationResultFactory $validationResultFactory, + AddressValidationRule $addressValidationRule, string $generalMessage = '' ) { $this->validationResultFactory = $validationResultFactory; + $this->addressValidationRule = $addressValidationRule; $this->generalMessage = $generalMessage; } @@ -47,6 +56,7 @@ public function validate(Quote $quote): array if (!$quote->isVirtual()) { $shippingAddress = $quote->getShippingAddress(); $shippingAddress->setStoreId($quote->getStoreId()); + $validationResult = $shippingAddress->validate(); if ($validationResult !== true) { $validationErrors = [__($this->generalMessage)]; @@ -54,6 +64,10 @@ public function validate(Quote $quote): array if (is_array($validationResult)) { $validationErrors = array_merge($validationErrors, $validationResult); } + + if (empty($validationErrors)) { + $this->addressValidationRule->validateAddress($shippingAddress, $validationErrors); + } } return [$this->validationResultFactory->create(['errors' => $validationErrors])]; diff --git a/app/code/Magento/Quote/etc/di.xml b/app/code/Magento/Quote/etc/di.xml index 04be517537b02..a0694ec752395 100644 --- a/app/code/Magento/Quote/etc/di.xml +++ b/app/code/Magento/Quote/etc/di.xml @@ -117,6 +117,7 @@ Magento\Quote\Model\ValidationRules\BillingAddressValidationRule Magento\Quote\Model\ValidationRules\PaymentMethodValidationRule Magento\Quote\Model\ValidationRules\MinimumAmountValidationRule + Magento\Quote\Model\ValidationRules\NameValidationRule @@ -145,6 +146,11 @@ Enter a valid payment method and try again. + + + Please check the name fields. + + diff --git a/app/code/Magento/Review/Model/Review.php b/app/code/Magento/Review/Model/Review.php index ef2474637f384..28f8ceada7109 100644 --- a/app/code/Magento/Review/Model/Review.php +++ b/app/code/Magento/Review/Model/Review.php @@ -14,6 +14,7 @@ use Magento\Framework\Validator\ValidatorChain; use Magento\Review\Model\ResourceModel\Review\Product\Collection as ProductCollection; use Magento\Review\Model\ResourceModel\Review\Status\Collection as StatusCollection; +use Magento\Framework\Validator\GlobalForbiddenPatterns; /** * Review model @@ -125,6 +126,13 @@ class Review extends \Magento\Framework\Model\AbstractModel implements IdentityI protected $_urlModel; /** + * @var GlobalForbiddenPatterns + */ + private $forbiddenPatternsValidator; + + /** + * Constructor. + * * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Review\Model\ResourceModel\Review\Product\CollectionFactory $productFactory @@ -134,6 +142,7 @@ class Review extends \Magento\Framework\Model\AbstractModel implements IdentityI * @param \Magento\Review\Model\Review\Summary $reviewSummary * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\UrlInterface $urlModel + * @param GlobalForbiddenPatterns $forbiddenPatternsValidator * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data @@ -149,6 +158,7 @@ public function __construct( \Magento\Review\Model\Review\Summary $reviewSummary, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\UrlInterface $urlModel, + GlobalForbiddenPatterns $forbiddenPatternsValidator, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] @@ -160,6 +170,7 @@ public function __construct( $this->_reviewSummary = $reviewSummary; $this->_storeManager = $storeManager; $this->_urlModel = $urlModel; + $this->forbiddenPatternsValidator = $forbiddenPatternsValidator; parent::__construct($context, $registry, $resource, $resourceCollection, $data); } @@ -292,9 +303,21 @@ public function validate() $errors[] = __('Please enter a review.'); } + // Validate fields with forbidden patterns + if (empty($errors)) { + $dataToValidate = [ + 'Title' => $this->getTitle(), + 'Nickname' => $this->getNickname(), + 'Detail' => $this->getDetail(), + ]; + + $this->forbiddenPatternsValidator->validateData($dataToValidate, $errors); + } + if (empty($errors)) { return true; } + return $errors; } diff --git a/app/code/Magento/Security/etc/adminhtml/system.xml b/app/code/Magento/Security/etc/adminhtml/system.xml index a31e1b1949b1a..afb51f0efeb94 100644 --- a/app/code/Magento/Security/etc/adminhtml/system.xml +++ b/app/code/Magento/Security/etc/adminhtml/system.xml @@ -57,6 +57,11 @@ Magento\Security\Model\Config\Backend\Session\SessionSize Limit the maximum session size in bytes. Use 0 to disable. + + + Magento\Config\Model\Config\Source\Yesno + Activate the extended field regex function. +
diff --git a/lib/internal/Magento/Framework/Validator/GlobalCityValidator.php b/lib/internal/Magento/Framework/Validator/GlobalCityValidator.php new file mode 100644 index 0000000000000..524dfd9937def --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/GlobalCityValidator.php @@ -0,0 +1,47 @@ +scopeConfig = $scopeConfig; + } + + /** + * Returns an array of forbidden patterns. + * + * @return string[] + */ + public function getPatterns(): array + { + return [ + '/{{.*}}/', + '/<\?=/', + '/<\?php/', + '/shell_exec/', + '/eval\(/', + '/\${IFS%/', + '/\bcurl\b/', + ]; + } + + /** + * Checks if the given field value is valid according to the forbidden patterns. + * + * @param mixed $fieldValue + * @return bool + */ + public function isValid(mixed $fieldValue): bool + { + if ($fieldValue === null || $fieldValue === '' || !is_string($fieldValue)) { + return true; + } + + $fieldValue = trim($fieldValue); + + foreach (self::getPatterns() as $pattern) { + if (preg_match($pattern, $fieldValue)) { + return false; + } + } + + // Check if the field contains a base64 encoded string and decode it for further validation + if (preg_match('/base64_decode\(/', $fieldValue)) { + $decodedValue = base64_decode($fieldValue); + // Recursively check the decoded value + return self::isValid($decodedValue); + } + + return true; + } + + /** + * Validate all fields in the provided data array based on forbidden patterns. + * + * @param array $data The data array to be validated. + * @param array &$validationErrors An array to collect validation errors. + * @return void + */ + public function validateData( + array $data, + array &$validationErrors + ): void { + $isRegexEnabled = $this->scopeConfig->isSetFlag( + self::XML_PATH_SECURITY_REGEX_ENABLED, + ScopeInterface::SCOPE_STORE + ); + + if ($isRegexEnabled) { + foreach ($data as $key => $value) { + if (is_string($value) && !$this->isValid($value)) { + $validationErrors[] = __("Field %1 contains invalid characters.", $key); + } + } + } + } +} diff --git a/lib/internal/Magento/Framework/Validator/GlobalNameValidator.php b/lib/internal/Magento/Framework/Validator/GlobalNameValidator.php new file mode 100644 index 0000000000000..0e5c3ae42f74c --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/GlobalNameValidator.php @@ -0,0 +1,51 @@ +