diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml index 699d0ee6fc5d4bdb2f57699eed91fd5232047198..ac54b6986d7d9947df50a1e7011285941adc527f 100644 --- a/core/modules/user/config/schema/user.schema.yml +++ b/core/modules/user/config/schema/user.schema.yml @@ -154,10 +154,14 @@ user.role.*: action.configuration.user_add_role_action: type: mapping label: 'Configuration for the add role action' + constraints: + FullyValidatable: ~ mapping: rid: type: string label: 'The ID of the role to add' + constraints: + RoleExists: ~ action.configuration.user_block_user_action: type: action_configuration_default @@ -170,10 +174,14 @@ action.configuration.user_cancel_user_action: action.configuration.user_remove_role_action: type: mapping label: 'Configuration for the remove role action' + constraints: + FullyValidatable: ~ mapping: rid: type: string label: 'The ID of the role to remove' + constraints: + RoleExists: ~ action.configuration.user_unblock_user_action: type: action_configuration_default diff --git a/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraint.php b/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraint.php new file mode 100644 index 0000000000000000000000000000000000000000..790cfc0f2ba726032821b3e3a5991cce8de26fb0 --- /dev/null +++ b/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraint.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\user\Plugin\Validation\Constraint; + +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\Validation\Attribute\Constraint; +use Symfony\Component\Validator\Constraint as SymfonyConstraint; + +/** + * Checks if a role exists. + */ +#[Constraint( + id: 'RoleExists', + label: new TranslatableMarkup('Role exists', [], ['context' => 'Validation']) +)] +class RoleExistsConstraint extends SymfonyConstraint { + + /** + * The error message if validation fails. + * + * @var string + */ + public $message = "The role with id '@rid' does not exist."; + +} diff --git a/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php b/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..a2652f742fe0b717184dac3dda052e0952e31d05 --- /dev/null +++ b/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php @@ -0,0 +1,54 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\user\Plugin\Validation\Constraint; + +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * Validates that a role exists. + */ +class RoleExistsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface { + + /** + * Create a new RoleExistsConstraintValidator instance. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + */ + public function __construct(private readonly EntityTypeManagerInterface $entity_type_manager) {} + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): static { + return new static( + $container->get(EntityTypeManagerInterface::class), + ); + } + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint): void { + assert($constraint instanceof RoleExistsConstraint); + + if (!is_string($value)) { + throw new UnexpectedTypeException($value, 'string'); + } + + $roleStorage = $this->entity_type_manager->getStorage('user_role'); + if (!$roleStorage->load($value)) { + $this->context->addViolation($constraint->message, [ + '@rid' => $value, + ]); + } + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/Validation/Constraint/RoleExistsConstraintValidatorTest.php b/core/modules/user/tests/src/Kernel/Plugin/Validation/Constraint/RoleExistsConstraintValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9baaa479eef6827194ff7aba7bf7ef963f8c8cf6 --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/Validation/Constraint/RoleExistsConstraintValidatorTest.php @@ -0,0 +1,65 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\user\Kernel\Plugin\Validation\Constraint; + +use Drupal\Core\TypedData\DataDefinition; +use Drupal\KernelTests\KernelTestBase; +use Drupal\user\Entity\Role; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @group user + * @group Validation + * + * @covers \Drupal\user\Plugin\Validation\Constraint\RoleExistsConstraint + * @covers \Drupal\user\Plugin\Validation\Constraint\RoleExistsConstraintValidator + */ +class RoleExistsConstraintValidatorTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['system', 'user']; + + /** + * Tests that the constraint validator will only work with strings. + */ + public function testValueMustBeAString(): void { + $definition = DataDefinition::create('any') + ->addConstraint('RoleExists'); + + $this->expectException(UnexpectedTypeException::class); + $this->expectExceptionMessage('Expected argument of type "string", "int" given'); + $this->container->get('typed_data_manager') + ->create($definition, 39) + ->validate(); + } + + /** + * Tests when the constraint's entityTypeId value is not valid. + */ + public function testRoleExists(): void { + // Validation error when role does not exist. + $definition = DataDefinition::create('string') + ->addConstraint('RoleExists'); + + $violations = $this->container->get('typed_data_manager') + ->create($definition, 'test_role') + ->validate(); + $this->assertEquals('The role with id \'test_role\' does not exist.', $violations->get(0)->getMessage()); + $this->assertCount(1, $violations); + + // Validation success when role exists. + Role::create(['id' => 'test_role', 'label' => 'Test role'])->save(); + $definition = DataDefinition::create('string') + ->addConstraint('RoleExists'); + + $violations = $this->container->get('typed_data_manager') + ->create($definition, 'test_role') + ->validate(); + $this->assertCount(0, $violations); + } + +}