From 8942ea517c72114a05d3ce3cf940c02d45014cd4 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Fri, 21 Feb 2025 16:33:28 +0100
Subject: [PATCH 1/8] feat: Add RoleExists validation

---
 .../user/config/schema/user.schema.yml        |  9 ++++
 .../Constraint/RoleExistsConstraint.php       | 27 ++++++++++
 .../RoleExistsConstraintValidator.php         | 54 +++++++++++++++++++
 3 files changed, 90 insertions(+)
 create mode 100644 core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraint.php
 create mode 100644 core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php

diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml
index 699d0ee6fc5d..81e60d91ce24 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,15 @@ 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'
+      # This might need to be "UserHasRole"?
+      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 000000000000..790cfc0f2ba7
--- /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 000000000000..2ac94ab38506
--- /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 Drupal\user\RoleStorageInterface;
+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
+   */
+  public function __construct(private readonly EntityTypeManagerInterface $entity_type_manager) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    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,
+      ]);
+    }
+  }
+
+}
-- 
GitLab


From 1adeac55e38b914cbeb19c1740681d61f9223edd Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Sat, 22 Feb 2025 09:08:51 +0100
Subject: [PATCH 2/8] build: fix codestyle

---
 .../Validation/Constraint/RoleExistsConstraintValidator.php   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php b/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php
index 2ac94ab38506..a2652f742fe0 100644
--- a/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php
+++ b/core/modules/user/src/Plugin/Validation/Constraint/RoleExistsConstraintValidator.php
@@ -6,7 +6,6 @@
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\user\RoleStorageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
@@ -21,13 +20,14 @@ class RoleExistsConstraintValidator extends ConstraintValidator implements Conta
    * 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) {
+  public static function create(ContainerInterface $container): static {
     return new static(
       $container->get(EntityTypeManagerInterface::class),
     );
-- 
GitLab


From b51a9b34204f6ef47454ce860e106c5751c9b3d8 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Sat, 22 Feb 2025 11:19:36 +0100
Subject: [PATCH 3/8] feat: Add RoleExistsConstraintValidatorTest

---
 .../RoleExistsConstraintValidatorTest.php     | 65 +++++++++++++++++++
 1 file changed, 65 insertions(+)
 create mode 100644 core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php

diff --git a/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php b/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
new file mode 100644
index 000000000000..1681561159a0
--- /dev/null
+++ b/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/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 Entity
+ * @group Validation
+ *
+ * @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\EntityBundleExistsConstraint
+ * @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\EntityBundleExistsConstraintValidator
+ */
+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);
+  }
+
+}
-- 
GitLab


From 203150c7201ee55001195dd309d022764314df19 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Sat, 22 Feb 2025 11:27:03 +0100
Subject: [PATCH 4/8] fix: remove extra comment in
 action.configuration.user_remove_role_action and created followup [#3508422]

---
 core/modules/user/config/schema/user.schema.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml
index 81e60d91ce24..ac54b6986d7d 100644
--- a/core/modules/user/config/schema/user.schema.yml
+++ b/core/modules/user/config/schema/user.schema.yml
@@ -180,7 +180,6 @@ action.configuration.user_remove_role_action:
     rid:
       type: string
       label: 'The ID of the role to remove'
-      # This might need to be "UserHasRole"?
       constraints:
         RoleExists: ~
 
-- 
GitLab


From b4b95d0f2863e0881a3a79290064904f15e29ff9 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Sat, 22 Feb 2025 11:44:25 +0100
Subject: [PATCH 5/8] fix: add group user to test

---
 .../Contraint/RoleExistsConstraintValidatorTest.php         | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php b/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
index 1681561159a0..9baaa479eef6 100644
--- a/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
+++ b/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
@@ -10,11 +10,11 @@
 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
 
 /**
- * @group Entity
+ * @group user
  * @group Validation
  *
- * @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\EntityBundleExistsConstraint
- * @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\EntityBundleExistsConstraintValidator
+ * @covers \Drupal\user\Plugin\Validation\Constraint\RoleExistsConstraint
+ * @covers \Drupal\user\Plugin\Validation\Constraint\RoleExistsConstraintValidator
  */
 class RoleExistsConstraintValidatorTest extends KernelTestBase {
 
-- 
GitLab


From 417c268f132a4fb6dbd925ea120168edbc0104d2 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Sat, 22 Feb 2025 11:50:58 +0100
Subject: [PATCH 6/8] chore: move test to non plugin namespace.

---
 .../Validation/Contraint/RoleExistsConstraintValidatorTest.php    | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename core/modules/user/tests/src/Kernel/{Plugin => }/Validation/Contraint/RoleExistsConstraintValidatorTest.php (100%)

diff --git a/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php b/core/modules/user/tests/src/Kernel/Validation/Contraint/RoleExistsConstraintValidatorTest.php
similarity index 100%
rename from core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
rename to core/modules/user/tests/src/Kernel/Validation/Contraint/RoleExistsConstraintValidatorTest.php
-- 
GitLab


From 8d69ea4512ac4c3060d7aa4c19f444e0bcbaffad Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Sat, 22 Feb 2025 11:54:27 +0100
Subject: [PATCH 7/8] Revert "chore: move test to non plugin namespace."

This reverts commit 417c268f132a4fb6dbd925ea120168edbc0104d2.
---
 .../Validation/Contraint/RoleExistsConstraintValidatorTest.php    | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename core/modules/user/tests/src/Kernel/{ => Plugin}/Validation/Contraint/RoleExistsConstraintValidatorTest.php (100%)

diff --git a/core/modules/user/tests/src/Kernel/Validation/Contraint/RoleExistsConstraintValidatorTest.php b/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
similarity index 100%
rename from core/modules/user/tests/src/Kernel/Validation/Contraint/RoleExistsConstraintValidatorTest.php
rename to core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
-- 
GitLab


From 7f8b65356b2f8b58dc8b02bb95018d61ef4eddf5 Mon Sep 17 00:00:00 2001
From: bjorn <bjorn@swis.nl>
Date: Sat, 22 Feb 2025 11:59:26 +0100
Subject: [PATCH 8/8] chore: rename directory to match namespace

---
 .../RoleExistsConstraintValidatorTest.php                         | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename core/modules/user/tests/src/Kernel/Plugin/Validation/{Contraint => Constraint}/RoleExistsConstraintValidatorTest.php (100%)

diff --git a/core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php b/core/modules/user/tests/src/Kernel/Plugin/Validation/Constraint/RoleExistsConstraintValidatorTest.php
similarity index 100%
rename from core/modules/user/tests/src/Kernel/Plugin/Validation/Contraint/RoleExistsConstraintValidatorTest.php
rename to core/modules/user/tests/src/Kernel/Plugin/Validation/Constraint/RoleExistsConstraintValidatorTest.php
-- 
GitLab