diff --git a/app/code/Magento/Contact/Model/Mail.php b/app/code/Magento/Contact/Model/Mail.php
index 43c1974252b5a..1baa1b7f476e5 100644
--- a/app/code/Magento/Contact/Model/Mail.php
+++ b/app/code/Magento/Contact/Model/Mail.php
@@ -1,7 +1,7 @@
contactsConfig = $contactsConfig;
$this->transportBuilder = $transportBuilder;
$this->inlineTranslation = $inlineTranslation;
$this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class);
+ $this->validatorFactory = $validatorFactory;
}
/**
@@ -59,9 +70,13 @@ public function __construct(
* @param string $replyTo
* @param array $variables
* @return void
+ * @throws ValidatorException
*/
public function send($replyTo, array $variables)
{
+ // Perform validation before sending email
+ $this->_validate($variables['data']);
+
/** @see \Magento\Contact\Controller\Index\Post::validatedParams() */
$replyToName = !empty($variables['data']['name']) ? $variables['data']['name'] : null;
@@ -86,4 +101,23 @@ public function send($replyTo, array $variables)
$this->inlineTranslation->resume();
}
}
+
+ /**
+ * Validate the contact form data.
+ *
+ * @param DataObject $data
+ * @throws ValidatorException
+ */
+ protected function _validate(DataObject $data)
+ {
+ $validator = $this->validatorFactory->createValidator('contact', 'save');
+
+ if (!$validator->isValid($data)) {
+ throw new ValidatorException(
+ null,
+ null,
+ $validator->getMessages()
+ );
+ }
+ }
}
diff --git a/app/code/Magento/Contact/Model/Validator/Email.php b/app/code/Magento/Contact/Model/Validator/Email.php
new file mode 100644
index 0000000000000..baf98d62254b0
--- /dev/null
+++ b/app/code/Magento/Contact/Model/Validator/Email.php
@@ -0,0 +1,98 @@
+emailValidator = $emailValidator;
+ }
+
+ /**
+ * Validate email fields.
+ *
+ * @param DataObject $data
+ * @return bool
+ */
+ public function isValid($data): bool
+ {
+ if (!$this->emailValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ $email = $data->getData('email');
+ if (empty($email)) {
+ return true;
+ }
+
+ if (!$this->validateEmailField($email)) {
+ return false;
+ }
+
+ return count($this->_messages) == 0;
+ }
+
+ /**
+ * Validate the email field.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ protected function validateEmailField(?string $emailValue): bool
+ {
+ if (!$this->emailValidator->isValid($emailValue)) {
+ parent::_addMessages(
+ [
+ __(
+ 'Email address is not valid! Allowed characters: %1',
+ $this->emailValidator->allowedCharsDescription
+ ),
+ ]
+ );
+ return false;
+ }
+
+ if ($this->isBlacklistEmail($emailValue)) {
+ parent::_addMessages([
+ __('The email address or domain is blacklisted.')
+ ]);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if email field is blacklisted using the EmailAddressValidator.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ protected function isBlacklistEmail(?string $emailValue): bool
+ {
+ return $this->emailValidator->isBlacklist($emailValue);
+ }
+}
diff --git a/app/code/Magento/Contact/Model/Validator/ForbiddenPattern.php b/app/code/Magento/Contact/Model/Validator/ForbiddenPattern.php
new file mode 100644
index 0000000000000..2bb404c846d16
--- /dev/null
+++ b/app/code/Magento/Contact/Model/Validator/ForbiddenPattern.php
@@ -0,0 +1,62 @@
+forbiddenValidator = $forbiddenValidator;
+ }
+
+ /**
+ * Validate contact form data fields against forbidden patterns.
+ *
+ * @param DataObject $data
+ * @return bool
+ * @throws LocalizedException
+ */
+ public function isValid($data): bool
+ {
+ if (!$this->forbiddenValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ $dataFields = $data->getData();
+ if (empty($dataFields)) {
+ return true;
+ }
+
+ $isValid = $this->forbiddenValidator->validateDataRecursively($dataFields);
+
+ if (!$isValid) {
+ parent::_addMessages([
+ __('Fraud Protection: Forbidden pattern detected in contact form data')
+ ]);
+ }
+
+ return count($this->_messages) == 0;
+ }
+}
diff --git a/app/code/Magento/Contact/etc/validation.xml b/app/code/Magento/Contact/etc/validation.xml
new file mode 100644
index 0000000000000..6bf33cc0e6217
--- /dev/null
+++ b/app/code/Magento/Contact/etc/validation.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Model/Validator/City.php b/app/code/Magento/Customer/Model/Validator/City.php
index 0b53551dfd88f..b2e8aedd8ca3e 100644
--- a/app/code/Magento/Customer/Model/Validator/City.php
+++ b/app/code/Magento/Customer/Model/Validator/City.php
@@ -1,7 +1,7 @@
cityValidator = $cityValidator;
+ }
/**
- * Validate city fields.
+ * Validate city field.
*
- * @param Customer $customer
+ * @param Customer $entity
* @return bool
*/
- public function isValid($customer)
+ public function isValid($entity): bool
{
- if (!$this->isValidCity($customer->getCity())) {
- parent::_addMessages([[
- 'city' => "Invalid City. Please use A-Z, a-z, 0-9, -, ', spaces"
- ]]);
+ if (!$this->cityValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ $cityField = $entity->getCity();
+ if (empty($cityField)) {
+ return true;
+ }
+
+ if (!$this->validateCityField($cityField)) {
+ parent::_addMessages(
+ [
+ __(
+ '%1 is not valid! Allowed characters: %2',
+ 'City',
+ $this->cityValidator->allowedCharsDescription
+ ),
+ ]
+ );
}
return count($this->_messages) == 0;
}
/**
- * Check if city field is valid.
+ * Validate the city field.
*
* @param string|null $cityValue
* @return bool
*/
- private function isValidCity($cityValue)
+ private function validateCityField(?string $cityValue): bool
{
- if ($cityValue != null) {
- if (preg_match(self::PATTERN_CITY, $cityValue, $matches)) {
- return $matches[0] == $cityValue;
- }
- }
-
- return true;
+ return $this->cityValidator->isValid($cityValue);
}
}
diff --git a/app/code/Magento/Customer/Model/Validator/Email.php b/app/code/Magento/Customer/Model/Validator/Email.php
new file mode 100644
index 0000000000000..be1d1905104fd
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/Email.php
@@ -0,0 +1,98 @@
+emailValidator = $emailValidator;
+ }
+
+ /**
+ * Validate email fields.
+ *
+ * @param Customer $customer
+ * @return bool
+ */
+ public function isValid($customer): bool
+ {
+ if (!$this->emailValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ $email = $customer->getEmail();
+ if (empty($email)) {
+ return true;
+ }
+
+ if (!$this->validateEmailField($email)) {
+ return false;
+ }
+
+ return count($this->_messages) == 0;
+ }
+
+ /**
+ * Validate the email field.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ private function validateEmailField(?string $emailValue): bool
+ {
+ if (!$this->emailValidator->isValid($emailValue)) {
+ parent::_addMessages(
+ [
+ __(
+ 'Email address is not valid! Allowed characters: %1',
+ $this->emailValidator->allowedCharsDescription
+ ),
+ ]
+ );
+ return false;
+ }
+
+ if ($this->isBlacklistEmail($emailValue)) {
+ parent::_addMessages([
+ __('The email address or domain is blacklisted.')
+ ]);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if email field is blacklisted using the EmailAddressValidator.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ private function isBlacklistEmail(?string $emailValue): bool
+ {
+ return $this->emailValidator->isBlacklist($emailValue);
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Validator/ForbiddenPattern.php b/app/code/Magento/Customer/Model/Validator/ForbiddenPattern.php
new file mode 100644
index 0000000000000..be7377e0e8e4a
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/ForbiddenPattern.php
@@ -0,0 +1,62 @@
+forbiddenValidator = $forbiddenValidator;
+ }
+
+ /**
+ * Validate EAV data fields against forbidden patterns.
+ *
+ * @param mixed $customer
+ * @return bool
+ * @throws LocalizedException
+ */
+ public function isValid($customer): bool
+ {
+ if (!$this->forbiddenValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ $customerData = $customer->getData();
+ if (empty($customerData)) {
+ return true;
+ }
+
+ $isValid = $this->forbiddenValidator->validateDataRecursively($customerData);
+
+ if (!$isValid) {
+ parent::_addMessages([
+ __('Fraud Protection: Forbidden pattern detected in customer data')
+ ]);
+ }
+
+ 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..ff714030cadb1 100644
--- a/app/code/Magento/Customer/Model/Validator/Name.php
+++ b/app/code/Magento/Customer/Model/Validator/Name.php
@@ -1,7 +1,7 @@
nameValidator = $nameValidator;
+ }
/**
* Validate name fields.
@@ -23,8 +37,8 @@ class Name extends AbstractValidator
* @param Customer $customer
* @return bool
*/
- public function isValid($customer)
- {
+ public function isValid($customer): bool
+ {
if (!$this->isValidName($customer->getFirstname())) {
parent::_addMessages([['firstname' => 'First Name is not valid!']]);
}
@@ -36,24 +50,18 @@ public function isValid($customer)
if (!$this->isValidName($customer->getMiddlename())) {
parent::_addMessages([['middlename' => 'Middle Name is not valid!']]);
}
-
+
return count($this->_messages) == 0;
}
/**
- * Check if name field is valid.
+ * Check if name field is valid using the NameValidator.
*
* @param string|null $nameValue
* @return bool
*/
- private function isValidName($nameValue)
+ private function isValidName($nameValue): bool
{
- if ($nameValue != null) {
- if (preg_match(self::PATTERN_NAME, $nameValue, $matches)) {
- return $matches[0] == $nameValue;
- }
- }
-
- return true;
+ return $this->nameValidator->isValid($nameValue);
}
}
diff --git a/app/code/Magento/Customer/Model/Validator/Pattern/CityValidator.php b/app/code/Magento/Customer/Model/Validator/Pattern/CityValidator.php
new file mode 100644
index 0000000000000..c188ec049533e
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/Pattern/CityValidator.php
@@ -0,0 +1,111 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Check if both the global security pattern and city validation are enabled in the configuration.
+ *
+ * @return bool
+ */
+ public function isValidationEnabled(): bool
+ {
+ $isGlobalPatternEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $isCityValidationEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_CITY_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ return $isGlobalPatternEnabled && $isCityValidationEnabled;
+ }
+
+ /**
+ * Validate the city value against the pattern.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ if ($value === null || $value === '' || !is_string($value)) {
+ return true;
+ }
+
+ return preg_match($this->patternCity, trim($value)) === 1;
+
+ return false;
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Validator/Pattern/EmailAddressValidator.php b/app/code/Magento/Customer/Model/Validator/Pattern/EmailAddressValidator.php
new file mode 100644
index 0000000000000..0a6d75505b2aa
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/Pattern/EmailAddressValidator.php
@@ -0,0 +1,143 @@
+scopeConfig = $scopeConfig;
+ $this->emailValidator = $emailValidator;
+ }
+
+ /**
+ * Check if both the global security pattern and email validation are enabled in the configuration.
+ *
+ * @return bool
+ */
+ public function isValidationEnabled(): bool
+ {
+ $isGlobalPatternEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $isEmailValidationEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_EMAIL_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ return $isGlobalPatternEnabled && $isEmailValidationEnabled;
+ }
+
+ /**
+ * Validate an email address.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ if ($value === null || $value === '' || !is_string($value)) {
+ return false;
+ }
+
+ if (!preg_match($this->patterEmail, trim($value))) {
+ return false;
+ }
+
+ return $this->emailValidator->isValid($value);
+ }
+
+ /**
+ * Check if the email address or its domain is blacklisted.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ public function isBlacklist(?string $emailValue): bool
+ {
+ if ($emailValue === null || $emailValue === '' || !is_string($emailValue)) {
+ return false;
+ }
+
+ if ($this->blacklistArray === null) {
+ $blacklist = $this->scopeConfig->getValue(self::XML_PATH_SECURITY_PATTERN_MAIL_BLACKLIST);
+ $this->blacklistArray = !empty($blacklist) ? preg_split('/[\r\n,]+/', $blacklist) : [];
+ }
+
+ $emailHost = substr(strrchr($emailValue, "@"), 1);
+
+ return in_array($emailValue, $this->blacklistArray) || in_array($emailHost, $this->blacklistArray);
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Validator/Pattern/ForbiddenValidator.php b/app/code/Magento/Customer/Model/Validator/Pattern/ForbiddenValidator.php
new file mode 100644
index 0000000000000..e552676de8f56
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/Pattern/ForbiddenValidator.php
@@ -0,0 +1,135 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Check if forbidden patterns validation is enabled.
+ *
+ * @return bool
+ */
+ public function isValidationEnabled(): bool
+ {
+ return $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_CODE_INJECTION_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+ }
+
+ /**
+ * Returns an array of forbidden patterns.
+ *
+ * @return string[]
+ */
+ public function getPatterns(): array
+ {
+ return [
+ '/{{.*}}/',
+ '/<\?=/',
+ '/<\?php/',
+ '/shell_exec/',
+ '/eval\(/',
+ '/\${IFS%/',
+ '/\bcurl\b/',
+ ];
+ }
+
+ /**
+ * Validates the given field value against forbidden patterns.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ if (!$this->isValidationEnabled()) {
+ return true;
+ }
+
+ return $this->validatePattern($value);
+ }
+
+ /**
+ * Recursively validate data against forbidden patterns.
+ *
+ * @param mixed $data
+ * @return bool
+ */
+ public function validateDataRecursively($data): bool
+ {
+ if (is_array($data)) {
+ foreach ($data as $value) {
+ if (!$this->validateDataRecursively($value)) {
+ return false;
+ }
+ }
+ } else {
+ return $this->isValid($data);
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the field value against forbidden patterns.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ private function validatePattern(mixed $value): bool
+ {
+ if (!is_string($value) || trim($value) === '') {
+ return true;
+ }
+
+ foreach ($this->getPatterns() as $pattern) {
+ if (preg_match($pattern, trim($value))) {
+ return false;
+ }
+ }
+
+ if (preg_match('/base64_decode\(/', $value)) {
+ // Use of base64_decode is discouraged, ensure this is safe in your context
+ // @codingStandardsIgnoreLine
+ $decodedValue = base64_decode($value);
+ return $this->validatePattern($decodedValue);
+ }
+
+ return true;
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Validator/Pattern/NameValidator.php b/app/code/Magento/Customer/Model/Validator/Pattern/NameValidator.php
new file mode 100644
index 0000000000000..e0986e9e04d7b
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/Pattern/NameValidator.php
@@ -0,0 +1,127 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Check if both the global security pattern and name validation are enabled in the configuration.
+ *
+ * @return bool
+ */
+ public function isValidationEnabled(): bool
+ {
+ $isGlobalPatternEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $isNameValidationEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_NAME_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ return $isGlobalPatternEnabled && $isNameValidationEnabled;
+ }
+
+ /**
+ * Validate the name value against the pattern.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ if ($value === null) {
+ return true;
+ }
+
+ $pattern = $this->isValidationEnabled() ? $this->patternName : self::PATTERN_NAME;
+
+ if (is_string($value)) {
+ $trimmedValue = trim($value);
+ if (preg_match($pattern, $trimmedValue, $matches)) {
+ return $matches[0] === $trimmedValue;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Validator/Pattern/StreetValidator.php b/app/code/Magento/Customer/Model/Validator/Pattern/StreetValidator.php
new file mode 100644
index 0000000000000..37b576354655d
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/Pattern/StreetValidator.php
@@ -0,0 +1,147 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Check if both the global security pattern and street validation are enabled in the configuration.
+ *
+ * @return bool
+ */
+ public function isValidationEnabled(): bool
+ {
+ $isGlobalPatternEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $isStreetValidationEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_STREET_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ return $isGlobalPatternEnabled && $isStreetValidationEnabled;
+ }
+
+ /**
+ * Validate a street address string or an array of street address strings.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ if (!$this->isValidationEnabled()) {
+ return true; // Skip validation if globally disabled
+ }
+
+ if (is_array($value)) {
+ foreach ($value as $streetValue) {
+ if (!$this->validateSingleStreet($streetValue)) {
+ return false;
+ }
+ }
+ } else {
+ if (!$this->validateSingleStreet($value)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate a single street address string.
+ *
+ * @param mixed $streetValue
+ * @return bool
+ */
+ private function validateSingleStreet($streetValue): bool
+ {
+ return $this->isValidStreet($streetValue);
+ }
+
+ /**
+ * Check if the street field is valid.
+ *
+ * @param mixed $streetValue
+ * @return bool
+ */
+ private function isValidStreet(mixed $streetValue): bool
+ {
+ if ($streetValue === null || $streetValue === '' || !is_string($streetValue)) {
+ return true;
+ }
+
+ return preg_match($this->patterStreet, trim($streetValue)) === 1;
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Validator/Pattern/TelephoneValidator.php b/app/code/Magento/Customer/Model/Validator/Pattern/TelephoneValidator.php
new file mode 100644
index 0000000000000..7bbd04085e720
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Validator/Pattern/TelephoneValidator.php
@@ -0,0 +1,109 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Check if both the global security pattern and telephone validation are enabled in the configuration.
+ *
+ * @return bool
+ */
+ public function isValidationEnabled(): bool
+ {
+ // Check if the global security pattern validation is enabled
+ $isGlobalPatternEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ // Check if the specific telephone validation is enabled
+ $isTelephoneValidationEnabled = $this->scopeConfig->isSetFlag(
+ self::XML_PATH_SECURITY_PATTERN_TELEPHONE_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ // Return true only if both are enabled
+ return $isGlobalPatternEnabled && $isTelephoneValidationEnabled;
+ }
+
+ /**
+ * Validate the telephone value against the pattern.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public function isValid($value): bool
+ {
+ if ($value === null || $value === '') {
+ return true;
+ }
+
+ return preg_match($this->patternTelephone, trim($value)) === 1;
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Validator/Street.php b/app/code/Magento/Customer/Model/Validator/Street.php
index 7de57d0ed32ef..5311eb526c2c6 100644
--- a/app/code/Magento/Customer/Model/Validator/Street.php
+++ b/app/code/Magento/Customer/Model/Validator/Street.php
@@ -1,7 +1,7 @@
streetValidator = $streetValidator;
+ }
/**
- * Validate street fields.
+ * Validate street field.
*
* @param Customer $customer
* @return bool
*/
- public function isValid($customer)
+ public function isValid($customer): bool
{
- foreach ($customer->getStreet() as $street) {
- if (!$this->isValidStreet($street)) {
- parent::_addMessages([[
- 'street' => "Invalid Street Address. Please use A-Z, a-z, 0-9, , - . ' ’ ` & spaces"
- ]]);
+ if (!$this->streetValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ $streets = $customer->getStreet();
+ if (empty($streets)) {
+ return true;
+ }
+
+ foreach ($streets as $street) {
+ if (!$this->validateStreetField($street)) {
+ parent::_addMessages(
+ [
+ 'street' => __(
+ 'Street is not valid! Allowed characters: %1',
+ $this->streetValidator->allowedCharsDescription
+ ),
+ ]
+ );
}
}
@@ -50,19 +65,13 @@ public function isValid($customer)
}
/**
- * Check if street field is valid.
+ * Validate the street field.
*
* @param string|null $streetValue
* @return bool
*/
- private function isValidStreet($streetValue)
+ private function validateStreetField(?string $streetValue): bool
{
- if ($streetValue != null) {
- if (preg_match(self::PATTERN_STREET, $streetValue, $matches)) {
- return $matches[0] == $streetValue;
- }
- }
-
- return true;
+ return $this->streetValidator->isValid($streetValue);
}
}
diff --git a/app/code/Magento/Customer/Model/Validator/Telephone.php b/app/code/Magento/Customer/Model/Validator/Telephone.php
index 0c85cb51f7e3d..78ba26854669d 100644
--- a/app/code/Magento/Customer/Model/Validator/Telephone.php
+++ b/app/code/Magento/Customer/Model/Validator/Telephone.php
@@ -1,7 +1,7 @@
telephoneValidator = $telephoneValidator;
+ }
+
/**
* Validate telephone fields.
*
* @param Customer $customer
* @return bool
*/
- public function isValid($customer)
+ public function isValid($customer): bool
{
- if (!$this->isValidTelephone($customer->getTelephone())) {
- parent::_addMessages([[
- 'telephone' => "Invalid Phone Number. Please use 0-9, +, -, (, ) and space."
- ]]);
+ if (!$this->telephoneValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ $telephoneFields = [
+ 'Phone Number' => $customer->getTelephone(),
+ 'Fax Number' => $customer->getFax()
+ ];
+
+ foreach ($telephoneFields as $fieldName => $fieldValue) {
+ if (!empty($fieldValue) && !$this->validateTelephoneField($fieldValue)) {
+ parent::_addMessages(
+ [
+ __(
+ '%1 is not valid! Allowed characters: %2',
+ $fieldName,
+ $this->telephoneValidator->allowedCharsDescription
+ ),
+ ]
+ );
+ }
}
return count($this->_messages) == 0;
}
/**
- * Check if telephone field is valid.
+ * Validate a single telephone field.
*
- * @param string|null $telephoneValue
+ * @param int|string|null $telephoneValue
* @return bool
*/
- private function isValidTelephone($telephoneValue)
+ private function validateTelephoneField(int|string|null $telephoneValue): bool
{
- if ($telephoneValue != null) {
- if (preg_match(self::PATTERN_TELEPHONE, (string) $telephoneValue, $matches)) {
- return $matches[0] == $telephoneValue;
- }
- }
-
- return true;
+ return $this->telephoneValidator->isValid($telephoneValue);
}
}
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 af46e7f5c7748..87626308e324d 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Validator/CityTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/CityTest.php
@@ -1,39 +1,47 @@
nameValidator = new City;
- $this->customerMock = $this
+ $this->cityValidatorMock = $this->createMock(CityValidator::class);
+ $this->cityValidator = new City($this->cityValidatorMock);
+
+ $this->addressMock = $this
->getMockBuilder(Customer::class)
->disableOriginalConstructor()
->addMethods(['getCity'])
@@ -41,45 +49,71 @@ protected function setUp(): void
}
/**
- * Test for allowed apostrophe and other punctuation characters in customer names
+ * Test for valid city name
*
* @param string $city
- * @param string $message
+ * @param bool $expectedIsValid
* @return void
- * @dataProvider expectedPunctuationInNamesDataProvider
+ * @dataProvider expectedPunctuationInCityDataProvider
*/
- public function testValidateCorrectPunctuationInNames(
+ public function testValidateCityName(
string $city,
- string $message
- ) {
- $this->customerMock->expects($this->once())->method('getCity')->willReturn($city);
+ bool $expectedIsValid
+ ): void {
+ $this->addressMock->expects($this->once())->method('getCity')->willReturn($city);
- $isValid = $this->nameValidator->isValid($this->customerMock);
- $this->assertTrue($isValid, $message);
+ $isValid = $this->cityValidator->isValid($this->addressMock);
+ $this->assertEquals($expectedIsValid, $isValid);
}
/**
+ * Data provider for city names
+ *
* @return array
*/
public static function expectedPunctuationInNamesDataProvider(): array
{
return [
+ [
+ 'city' => 'New York',
+ 'expectedIsValid' => true,
+ 'message' => 'Spaces must be allowed in city names'
+ ],
+ [
+ 'city' => 'São Paulo',
+ 'expectedIsValid' => true,
+ 'message' => 'Accented characters and spaces must be allowed in city names'
+ ],
+ [
+ 'city' => 'St. Louis',
+ 'expectedIsValid' => true,
+ 'message' => 'Periods and spaces must be allowed in city names'
+ ],
[
'city' => 'Москва',
- 'message' => 'Unicode letters must be allowed in city'
+ 'expectedIsValid' => true,
+ 'message' => 'Unicode letters must be allowed in city names'
+ ],
+ [
+ 'city' => 'Moscow \'',
+ 'expectedIsValid' => true,
+ 'message' => 'Apostrophe characters must be allowed in city names'
],
[
- 'city' => 'Мо́сква',
- 'message' => 'Unicode marks must be allowed in city'
+ 'city' => 'St.-Pierre',
+ 'expectedIsValid' => true,
+ 'message' => 'Hyphens must be allowed in city names'
],
[
- 'city' => ' Moscow \'',
- 'message' => 'Apostrophe characters must be allowed in city'
+ 'city' => 'Offenbach (Main)',
+ 'expectedIsValid' => true,
+ 'message' => 'Parentheses must be allowed in city names'
],
[
- 'city' => ' Moscow Moscow',
- 'message' => 'Whitespace characters must be allowed in city'
- ]
+ 'city' => 'Rome: The Eternal City',
+ 'expectedIsValid' => true,
+ '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 c141b3baa423f..a9f1be62b3704 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Validator/NameTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/NameTest.php
@@ -1,7 +1,7 @@
nameValidator = new Name;
+ $this->nameValidatorMock = $this->createMock(NameValidator::class);
+ $this->nameValidator = new Name($this->nameValidatorMock);
$this->customerMock = $this
->getMockBuilder(Customer::class)
->disableOriginalConstructor()
@@ -41,7 +48,7 @@ protected function setUp(): void
}
/**
- * Test for allowed apostrophe and other punctuation characters in customer names
+ * Test for allowed punctuation characters in customer names
*
* @param string $firstName
* @param string $middleName
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 e8d4f7b61be7a..85868689dd87a 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Validator/StreetTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/StreetTest.php
@@ -1,39 +1,47 @@
nameValidator = new Street;
- $this->customerMock = $this
+ $this->streetValidatorMock = $this->createMock(StreetValidator::class);
+ $this->streetValidator = new Street($this->streetValidatorMock);
+
+ $this->addressMock = $this
->getMockBuilder(Customer::class)
->disableOriginalConstructor()
->addMethods(['getStreet'])
@@ -41,24 +49,26 @@ protected function setUp(): void
}
/**
- * Test for allowed apostrophe and other punctuation characters in customer names
+ * Test for valid street name
*
* @param array $street
* @param string $message
* @return void
- * @dataProvider expectedPunctuationInNamesDataProvider
+ * @dataProvider expectedPunctuationInStreetDataProvider
*/
- public function testValidateCorrectPunctuationInNames(
+ public function testValidateStreetName(
array $street,
string $message
- ) {
- $this->customerMock->expects($this->once())->method('getStreet')->willReturn($street);
+ ): void {
+ $this->addressMock->expects($this->once())->method('getStreet')->willReturn($street);
- $isValid = $this->nameValidator->isValid($this->customerMock);
+ $isValid = $this->streetValidator->isValid($this->addressMock);
$this->assertTrue($isValid, $message);
}
/**
+ * Data provider for street names
+ *
* @return array
*/
public static function expectedPunctuationInNamesDataProvider(): array
@@ -102,7 +112,7 @@ public static 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 +137,14 @@ public static 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 859c240764e57..374622fecd8d3 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Validator/TelephoneTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Validator/TelephoneTest.php
@@ -1,7 +1,7 @@
nameValidator = new Telephone;
+ $this->telephoneValidatorMock = $this->createMock(TelephoneValidator::class);
+ $this->telephoneValidator = new Telephone($this->telephoneValidatorMock);
$this->customerMock = $this
->getMockBuilder(Customer::class)
->disableOriginalConstructor()
- ->addMethods(['getTelephone'])
+ ->addMethods(['getTelephone', 'getFax'])
->getMock();
}
/**
- * Test for allowed apostrophe and other punctuation characters in customer names
+ * Test for allowed punctuation characters in customer telephone numbers
*
* @param string $telephone
+ * @param string $fax
* @param string $message
* @return void
- * @dataProvider expectedPunctuationInNamesDataProvider
+ * @dataProvider expectedPunctuationInTelephoneDataProvider
*/
- public function testValidateCorrectPunctuationInNames(
+ public function testValidateCorrectPunctuationInTelephone(
string $telephone,
+ string $fax,
string $message
- ) {
+ ): void {
$this->customerMock->expects($this->once())->method('getTelephone')->willReturn($telephone);
+ $this->customerMock->expects($this->once())->method('getFax')->willReturn($fax);
- $isValid = $this->nameValidator->isValid($this->customerMock);
+ $isValid = $this->telephoneValidator->isValid($this->customerMock);
$this->assertTrue($isValid, $message);
}
@@ -66,19 +76,18 @@ public static function expectedPunctuationInNamesDataProvider(): array
return [
[
'telephone' => '(1)99887766',
- 'message' => 'parentheses must be allowed in telephone'
+ 'fax' => '123456789',
+ 'message' => 'Parentheses must be allowed in telephone, and digits must be allowed in fax.'
],
[
'telephone' => '+6255554444',
- 'message' => 'plus sign be allowed in telephone'
+ 'fax' => '123 456 789',
+ 'message' => 'Plus sign must be allowed in telephone, and spaces must be allowed in fax.'
],
[
'telephone' => '555-555-555',
- 'message' => 'hyphen must be allowed in telephone'
- ],
- [
- 'telephone' => '123456789',
- 'message' => 'Digits (numbers) must be allowed in telephone'
+ 'fax' => '123/456/789',
+ 'message' => 'Hyphen must be allowed in telephone, and forward slashes must be allowed in fax.'
]
];
}
diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml
index ec76e09fdf459..de12213ba5f4e 100644
--- a/app/code/Magento/Customer/etc/adminhtml/system.xml
+++ b/app/code/Magento/Customer/etc/adminhtml/system.xml
@@ -1,8 +1,8 @@
@@ -313,5 +313,67 @@
+
+
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+ Activate the extended field pattern function.
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
+ Enable or disable pattern validation for city fields.
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
+ Enable or disable pattern validation for name fields.
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
+ Enable or disable pattern validation for telephone fields.
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
+ Enable or disable pattern validation for street fields.
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
+ Enable or disable pattern validation for e-mail fields.
+
+
+
+
+ 1
+
+ Enter one E-Mail or Host per line for banned validation.
+
+
+
+ Magento\Config\Model\Config\Source\Yesno
+ Activate the extended pattern function to limit code injection.
+
+
+
diff --git a/app/code/Magento/Customer/etc/validation.xml b/app/code/Magento/Customer/etc/validation.xml
index 7fd6cfeb79472..225d22a46ff89 100644
--- a/app/code/Magento/Customer/etc/validation.xml
+++ b/app/code/Magento/Customer/etc/validation.xml
@@ -1,8 +1,8 @@
@@ -23,12 +23,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -38,7 +56,6 @@
-
@@ -51,9 +68,14 @@
-
+
-
+
+
+
+
+
+
@@ -61,9 +83,14 @@
-
+
-
+
+
+
+
+
+
@@ -72,9 +99,11 @@
+
+
+
-
diff --git a/app/code/Magento/Newsletter/Model/SubscriptionManager.php b/app/code/Magento/Newsletter/Model/SubscriptionManager.php
index 05be8325e3243..3af18adaad521 100644
--- a/app/code/Magento/Newsletter/Model/SubscriptionManager.php
+++ b/app/code/Magento/Newsletter/Model/SubscriptionManager.php
@@ -1,7 +1,7 @@
logger = $logger;
$this->storeManager = $storeManager;
$this->scopeConfig = $scopeConfig;
+ $this->validatorFactory = $validatorFactory;
$this->customerAccountManagement = $customerAccountManagement;
$this->customerRepository = $customerRepository;
$this->customerSubscriberCache = $customerSubscriberCache
@@ -90,6 +100,10 @@ public function __construct(
*/
public function subscribe(string $email, int $storeId): Subscriber
{
+ if ($email) {
+ $this->_validate($email);
+ }
+
$websiteId = (int)$this->storeManager->getStore($storeId)->getWebsiteId();
$subscriber = $this->subscriberFactory->create()->loadBySubscriberEmail($email, $websiteId);
$currentStatus = (int)$subscriber->getStatus();
@@ -111,6 +125,28 @@ public function subscribe(string $email, int $storeId): Subscriber
return $subscriber;
}
+ /**
+ * Validate the subscriber's email for guest subscribers.
+ *
+ * @param string $email
+ * @return void
+ * @throws ValidatorException
+ */
+ protected function _validate(string $email): void
+ {
+ // Create the validator using the entity and group defined in the XML
+ $validator = $this->validatorFactory->createValidator('newsletter_subscriber', 'save');
+
+ // Check if the email is valid
+ if (!$validator->isValid($email)) {
+ throw new ValidatorException(
+ null,
+ null,
+ $validator->getMessages()
+ );
+ }
+ }
+
/**
* @inheritdoc
*/
diff --git a/app/code/Magento/Newsletter/Model/Validator/Email.php b/app/code/Magento/Newsletter/Model/Validator/Email.php
new file mode 100644
index 0000000000000..6d695577eaaea
--- /dev/null
+++ b/app/code/Magento/Newsletter/Model/Validator/Email.php
@@ -0,0 +1,93 @@
+emailValidator = $emailValidator;
+ }
+
+ /**
+ * Validate email fields.
+ *
+ * @param string $email
+ * @return bool
+ */
+ public function isValid($email): bool
+ {
+ if (!$this->emailValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ if (!$this->validateEmailField($email)) {
+ return false;
+ }
+
+ return count($this->_messages) == 0;
+ }
+
+ /**
+ * Validate the email field.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ private function validateEmailField(?string $emailValue): bool
+ {
+ if (!$this->emailValidator->isValid($emailValue)) {
+ parent::_addMessages(
+ [
+ __(
+ 'Email address is not valid! Allowed characters: %1',
+ $this->emailValidator->allowedCharsDescription
+ ),
+ ]
+ );
+ return false;
+ }
+
+ if ($this->isBlacklistEmail($emailValue)) {
+ parent::_addMessages([
+ __('The email address or domain is blacklisted.')
+ ]);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if email field is blacklisted using the EmailAddressValidator.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ private function isBlacklistEmail(?string $emailValue): bool
+ {
+ return $this->emailValidator->isBlacklist($emailValue);
+ }
+}
diff --git a/app/code/Magento/Newsletter/etc/validation.xml b/app/code/Magento/Newsletter/etc/validation.xml
new file mode 100644
index 0000000000000..2721621d5509c
--- /dev/null
+++ b/app/code/Magento/Newsletter/etc/validation.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Review/Model/Review.php b/app/code/Magento/Review/Model/Review.php
index ef2474637f384..e8f22c8e38da1 100644
--- a/app/code/Magento/Review/Model/Review.php
+++ b/app/code/Magento/Review/Model/Review.php
@@ -1,7 +1,7 @@
_reviewSummary = $reviewSummary;
$this->_storeManager = $storeManager;
$this->_urlModel = $urlModel;
+ $this->validatorFactory = $validatorFactory;
parent::__construct($context, $registry, $resource, $resourceCollection, $data);
}
@@ -292,12 +301,32 @@ public function validate()
$errors[] = __('Please enter a review.');
}
+ $this->_validate();
+
if (empty($errors)) {
return true;
}
return $errors;
}
-
+
+ /**
+ * Validate review using custom validator.
+ *
+ * @throws ValidatorException
+ */
+ protected function _validate()
+ {
+ $validator = $this->validatorFactory->createValidator('review', 'save');
+
+ if (!$validator->isValid($this)) {
+ throw new ValidatorException(
+ null,
+ null,
+ $validator->getMessages()
+ );
+ }
+ }
+
/**
* Perform actions after object delete
*
diff --git a/app/code/Magento/Review/Model/Validator/Email.php b/app/code/Magento/Review/Model/Validator/Email.php
new file mode 100644
index 0000000000000..7a66948865705
--- /dev/null
+++ b/app/code/Magento/Review/Model/Validator/Email.php
@@ -0,0 +1,94 @@
+emailValidator = $emailValidator;
+ }
+
+ /**
+ * Validate email fields.
+ *
+ * @param string $email
+ * @return bool
+ */
+ public function isValid($email): bool
+ {
+ if (!$this->emailValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ if (!$this->validateEmailField($email)) {
+ return false;
+ }
+
+ return count($this->_messages) == 0;
+ }
+
+ /**
+ * Validate the email field.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ private function validateEmailField(?string $emailValue): bool
+ {
+ if (!$this->emailValidator->isValid($emailValue)) {
+ parent::_addMessages(
+ [
+ __(
+ 'Email address is not valid! Allowed characters: %1',
+ $this->emailValidator->allowedCharsDescription
+ ),
+ ]
+ );
+ return false;
+ }
+
+ if ($this->isBlacklistEmail($emailValue)) {
+ parent::_addMessages([
+ __('The email address or domain is blacklisted.')
+ ]);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if email field is blacklisted using the EmailAddressValidator.
+ *
+ * @param string|null $emailValue
+ * @return bool
+ */
+ private function isBlacklistEmail(?string $emailValue): bool
+ {
+ return $this->emailValidator->isBlacklist($emailValue);
+ }
+}
diff --git a/app/code/Magento/Review/Model/Validator/ForbiddenPattern.php b/app/code/Magento/Review/Model/Validator/ForbiddenPattern.php
new file mode 100644
index 0000000000000..36e9f1f70b6ea
--- /dev/null
+++ b/app/code/Magento/Review/Model/Validator/ForbiddenPattern.php
@@ -0,0 +1,60 @@
+forbiddenValidator = $forbiddenValidator;
+ }
+
+ /**
+ * Validate multiple review fields against forbidden patterns.
+ *
+ * @param array $values
+ * @return bool
+ */
+ public function isValid($values): bool
+ {
+ if (!$this->forbiddenValidator->isValidationEnabled()) {
+ return true;
+ }
+
+ foreach ($values as $field => $value) {
+ if (empty($value)) {
+ continue;
+ }
+
+ if (!$this->forbiddenValidator->isValid($value)) {
+ parent::_addMessages([
+ __("Fraud Protection: Forbidden pattern detected in review field")
+ ]);
+ }
+ }
+
+ return count($this->_messages) == 0;
+ }
+}
diff --git a/app/code/Magento/Review/etc/validation.xml b/app/code/Magento/Review/etc/validation.xml
new file mode 100644
index 0000000000000..2e39bd21de89e
--- /dev/null
+++ b/app/code/Magento/Review/etc/validation.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+