Issue #3431203 by kim.pepper, alexpott, smustgrave, longwave: Deprecate user_validate_name() and replace with service

merge-requests/6839/merge
Alex Pott 2024-04-05 10:12:07 +01:00
parent 644d8f2abd
commit 8e7d3e0f52
No known key found for this signature in database
GPG Key ID: BDA67E7EE836E5CE
8 changed files with 198 additions and 32 deletions

View File

@ -8,8 +8,9 @@ use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Locale\CountryManagerInterface;
use Drupal\Core\Site\Settings;
use Drupal\user\UserStorageInterface;
use Drupal\user\UserInterface;
use Drupal\user\UserStorageInterface;
use Drupal\user\UserNameValidator;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -67,13 +68,26 @@ class SiteConfigureForm extends ConfigFormBase {
* The module installer.
* @param \Drupal\Core\Locale\CountryManagerInterface $country_manager
* The country manager.
* @param \Drupal\user\UserNameValidator|null $userNameValidator
* The user validator.
*/
public function __construct($root, $site_path, UserStorageInterface $user_storage, ModuleInstallerInterface $module_installer, CountryManagerInterface $country_manager) {
public function __construct(
$root,
$site_path,
UserStorageInterface $user_storage,
ModuleInstallerInterface $module_installer,
CountryManagerInterface $country_manager,
protected ?UserNameValidator $userNameValidator = NULL,
) {
$this->root = $root;
$this->sitePath = $site_path;
$this->userStorage = $user_storage;
$this->moduleInstaller = $module_installer;
$this->countryManager = $country_manager;
if (!$userNameValidator) {
@\trigger_error('Calling ' . __METHOD__ . ' without the $userNameValidator argument is deprecated in drupal:10.3.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3431205', E_USER_DEPRECATED);
$this->userNameValidator = \Drupal::service('user.name_validator');
}
}
/**
@ -85,7 +99,8 @@ class SiteConfigureForm extends ConfigFormBase {
$container->getParameter('site.path'),
$container->get('entity_type.manager')->getStorage('user'),
$container->get('module_installer'),
$container->get('country_manager')
$container->get('country_manager'),
$container->get('user.name_validator'),
);
}
@ -253,8 +268,9 @@ class SiteConfigureForm extends ConfigFormBase {
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if ($error = user_validate_name($form_state->getValue(['account', 'name']))) {
$form_state->setErrorByName('account][name', $error);
$violations = $this->userNameValidator->validateName($form_state->getValue(['account', 'name']));
if ($violations->count() > 0) {
$form_state->setErrorByName('account][name', $violations[0]->getMessage());
}
}

View File

@ -4,7 +4,7 @@ namespace Drupal\user\Form;
use Drupal\Component\Utility\EmailValidatorInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
use Drupal\Core\Flood\FloodInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
@ -13,6 +13,7 @@ use Drupal\Core\Render\Element\Email;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\user\UserInterface;
use Drupal\user\UserStorageInterface;
use Drupal\user\UserNameValidator;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -24,6 +25,15 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class UserPasswordForm extends FormBase {
use DeprecatedServicePropertyTrait;
/**
* The deprecated properties.
*/
protected array $deprecatedProperties = [
'typedDataManager' => 'typed_data_manager',
];
/**
* The user storage.
*
@ -45,13 +55,6 @@ class UserPasswordForm extends FormBase {
*/
protected $flood;
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManagerInterface
*/
protected $typedDataManager;
/**
* The email validator service.
*
@ -70,18 +73,28 @@ class UserPasswordForm extends FormBase {
* The config factory.
* @param \Drupal\Core\Flood\FloodInterface $flood
* The flood service.
* @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
* The typed data manager.
* @param \Drupal\user\UserNameValidator|\Drupal\Core\TypedData\TypedDataManagerInterface $userNameValidator
* The user validator service.
* @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
* The email validator service.
*/
public function __construct(UserStorageInterface $user_storage, LanguageManagerInterface $language_manager, ConfigFactory $config_factory, FloodInterface $flood, TypedDataManagerInterface $typed_data_manager, EmailValidatorInterface $email_validator) {
public function __construct(
UserStorageInterface $user_storage,
LanguageManagerInterface $language_manager,
ConfigFactory $config_factory,
FloodInterface $flood,
protected UserNameValidator|TypedDataManagerInterface $userNameValidator,
EmailValidatorInterface $email_validator,
) {
$this->userStorage = $user_storage;
$this->languageManager = $language_manager;
$this->configFactory = $config_factory;
$this->flood = $flood;
$this->typedDataManager = $typed_data_manager;
$this->emailValidator = $email_validator;
if (!$userNameValidator instanceof UserNameValidator) {
@\trigger_error('Passing $userNameValidator as \Drupal\Core\TypedData\TypedDataManagerInterface to ' . __METHOD__ . ' () is deprecated in drupal:10.3.0 and is removed in drupal:10.0.0. Pass a Drupal\user\UserValidator instead. See https://www.drupal.org/node/3431205', E_USER_DEPRECATED);
$this->userNameValidator = \Drupal::service('user.name_validator');
}
}
/**
@ -93,8 +106,8 @@ class UserPasswordForm extends FormBase {
$container->get('language_manager'),
$container->get('config.factory'),
$container->get('flood'),
$container->get('typed_data_manager'),
$container->get('email.validator')
$container->get('user.name_validator'),
$container->get('email.validator'),
);
}
@ -161,11 +174,7 @@ class UserPasswordForm extends FormBase {
$this->flood->register('user.password_request_ip', $flood_config->get('ip_window'));
// First, see if the input is possibly valid as a username.
$name = trim($form_state->getValue('name'));
$definition = BaseFieldDefinition::create('string')
->addConstraint('UserName', []);
$data = $this->typedDataManager->create($definition);
$data->setValue($name);
$violations = $data->validate();
$violations = $this->userNameValidator->validateName($name);
// Usernames have a maximum length shorter than email addresses. Only print
// this error if the input is not valid as a username or email address.
if ($violations->count() > 0 && !$this->emailValidator->isValid($name)) {

View File

@ -2,6 +2,7 @@
namespace Drupal\user\Plugin\Validation\Constraint;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\user\UserInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
@ -15,11 +16,11 @@ class UserNameConstraintValidator extends ConstraintValidator {
* {@inheritdoc}
*/
public function validate($items, Constraint $constraint): void {
if (!isset($items) || !$items->value) {
if (empty($items) || ($items instanceof FieldItemListInterface && $items->isEmpty())) {
$this->context->addViolation($constraint->emptyMessage);
return;
}
$name = $items->first()->value;
$name = $items instanceof FieldItemListInterface ? $items->first()->value : $items;
if (str_starts_with($name, ' ')) {
$this->context->addViolation($constraint->spaceBeginMessage);
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\user;
use Drupal\Core\Validation\BasicRecursiveValidatorFactory;
use Drupal\Core\Validation\ConstraintManager;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* Provides a username validator.
*
* This validator re-uses the UserName constraint plugin but does not require a
* User entity.
*/
class UserNameValidator {
public function __construct(
protected readonly BasicRecursiveValidatorFactory $validatorFactory,
protected readonly ConstraintManager $constraintManager,
) {}
/**
* Validates a user name.
*
* @return \Symfony\Component\Validator\ConstraintViolationListInterface
* The list of constraint violations.
*/
public function validateName(string $name): ConstraintViolationListInterface {
$validator = $this->validatorFactory->createValidator();
$constraint = $this->constraintManager->create('UserName', []);
return $validator->validate($name, $constraint);
}
}

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\user\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\UserInterface;
use Drupal\user\UserNameValidator;
/**
* Verify that user validity checks behave as designed.
*
* @group user
*/
class UserNameValidatorTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['user'];
/**
* The user validator under test.
*/
protected UserNameValidator $userValidator;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->userValidator = $this->container->get('user.name_validator');
}
/**
* Tests valid user name validation.
*
* @dataProvider validUsernameProvider
*/
public function testValidUsernames($name): void {
$violations = $this->userValidator->validateName($name);
$this->assertEmpty($violations);
}
/**
* Tests invalid user name validation.
*
* @dataProvider invalidUserNameProvider
*/
public function testInvalidUsernames($name, $expectedMessage): void {
$violations = $this->userValidator->validateName($name);
$this->assertNotEmpty($violations);
$this->assertEquals($expectedMessage, $violations[0]->getMessage());
}
/**
* Provides valid user names.
*/
public static function validUsernameProvider(): array {
// cSpell:disable
return [
'lowercase' => ['foo'],
'uppercase' => ['FOO'],
'contains space' => ['Foo O\'Bar'],
'contains @' => ['foo@bar'],
'allow email' => ['foo@example.com'],
'allow invalid domain' => ['foo@-example.com'],
'allow special chars' => ['þòøÇߪř€'],
'allow plus' => ['foo+bar'],
'utf8 runes' => ['ᚠᛇᚻ᛫ᛒᛦᚦ'],
];
// cSpell:enable
}
/**
* Provides invalid user names.
*/
public static function invalidUserNameProvider(): array {
return [
'starts with space' => [' foo', 'The username cannot begin with a space.'],
'ends with space' => ['foo ', 'The username cannot end with a space.'],
'contains 2 spaces' => ['foo bar', 'The username cannot contain multiple spaces in a row.'],
'empty string' => ['', 'You must enter a username.'],
'invalid chars' => ['foo/', 'The username contains an illegal character.'],
// NULL.
'contains chr(0)' => ['foo' . chr(0) . 'bar', 'The username contains an illegal character.'],
// CR.
'contains chr(13)' => ['foo' . chr(13) . 'bar', 'The username contains an illegal character.'],
'excessively long' => [str_repeat('x', UserInterface::USERNAME_MAX_LENGTH + 1),
'The username xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx is too long: it must be 60 characters or less.',
],
];
}
}

View File

@ -38,6 +38,8 @@ class UserValidationTest extends KernelTestBase {
/**
* Tests user name validation.
*
* @group legacy
*/
public function testUsernames() {
// cSpell:disable
@ -66,6 +68,7 @@ class UserValidationTest extends KernelTestBase {
'foo' . chr(13) . 'bar' => ['Invalid username containing chr(13)', 'assertNotNull'],
str_repeat('x', UserInterface::USERNAME_MAX_LENGTH + 1) => ['Invalid excessively long username', 'assertNotNull'],
];
$this->expectDeprecation('user_validate_name() is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\user\UserValidator::validateName() instead. See https://www.drupal.org/node/3431205');
// cSpell:enable
foreach ($test_cases as $name => $test_case) {
[$description, $test] = $test_case;

View File

@ -14,7 +14,6 @@ use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
@ -206,13 +205,15 @@ function user_load_by_name($name) {
* @return string|null
* A translated violation message if the name is invalid or NULL if the name
* is valid.
*
* @deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use
* \Drupal\user\UserValidator::validateName() instead.
*
* @see https://www.drupal.org/node/3431205
*/
function user_validate_name($name) {
$definition = BaseFieldDefinition::create('string')
->addConstraint('UserName', []);
$data = \Drupal::typedDataManager()->create($definition);
$data->setValue($name);
$violations = $data->validate();
@trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\user\UserValidator::validateName() instead. See https://www.drupal.org/node/3431205', E_USER_DEPRECATED);
$violations = \Drupal::service('user.name_validator')->validateName($name);
if (count($violations) > 0) {
return $violations[0]->getMessage();
}

View File

@ -73,3 +73,7 @@ services:
user.module_permissions_link_helper:
class: Drupal\user\ModulePermissionsLinkHelper
arguments: ['@user.permissions', '@access_manager', '@extension.list.module']
user.name_validator:
class: Drupal\user\UserNameValidator
arguments: ['@validation.basic_recursive_validator_factory', '@validation.constraint']
Drupal\user\UserNameValidator: '@user.name_validator'