From c6b4d6c3d359ce9bb3165757a21237de13d08f4d Mon Sep 17 00:00:00 2001 From: Dave Long <dave@longwaveconsulting.com> Date: Wed, 20 Nov 2024 17:59:41 +0000 Subject: [PATCH] SA-CORE-2024-004 by zengenuity, cilefen, kristiaanvandeneynde, mcdruid, larowlan --- .../Constraint/UniqueFieldConstraint.php | 10 ++++++ .../Constraint/UniqueFieldValueValidator.php | 13 ++++++- .../Validation/Constraint/FileUriUnique.php | 19 ++++++++--- core/modules/user/user.install | 34 ++++++++++++++----- 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php index 9196e0b86abb..7b121a5bf64c 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldConstraint.php @@ -17,6 +17,16 @@ class UniqueFieldConstraint extends SymfonyConstraint { public $message = 'A @entity_type with @field_name %value already exists.'; + /** + * This constraint is case-insensitive by default. + * + * For example "FOO" and "foo" would be considered as equivalent, and + * validation of the constraint would fail. + * + * @var bool + */ + public $caseSensitive = FALSE; + /** * Returns the name of the class that validates this constraint. * diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php index 430b0793cdd7..ec0958002802 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php @@ -64,12 +64,23 @@ public function validate($items, Constraint $constraint) { ->getStorage($entity_type_id) ->getAggregateQuery() ->accessCheck(FALSE) - ->condition($field_name, $item_values, 'IN') ->groupBy("$field_name.$property_name"); if (!$is_new) { $entity_id = $entity->id(); $query->condition($id_key, $entity_id, '<>'); } + + if ($constraint->caseSensitive) { + $query->condition($field_name, $item_values, 'IN'); + } + else { + $or_group = $query->orConditionGroup(); + foreach ($item_values as $item_value) { + $or_group->condition($field_name, \Drupal::database()->escapeLike($item_value), 'LIKE'); + } + $query->condition($or_group); + } + $results = $query->execute(); if (!empty($results)) { diff --git a/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php b/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php index 855766f99de8..fd2300a7ca18 100644 --- a/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileUriUnique.php @@ -4,7 +4,8 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Validation\Attribute\Constraint; -use Symfony\Component\Validator\Constraint as SymfonyConstraint; +use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint; +use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator; /** * Supports validating file URIs. @@ -13,15 +14,25 @@ id: 'FileUriUnique', label: new TranslatableMarkup('File URI', [], ['context' => 'Validation']) )] -class FileUriUnique extends SymfonyConstraint { +class FileUriUnique extends UniqueFieldConstraint { public $message = 'The file %value already exists. Enter a unique file URI.'; + /** + * This constraint is case-sensitive. + * + * For example "public://foo.txt" and "public://FOO.txt" are treated as + * different values, and can co-exist. + * + * @var bool + */ + public $caseSensitive = TRUE; + /** * {@inheritdoc} */ - public function validatedBy() { - return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator'; + public function validatedBy(): string { + return UniqueFieldValueValidator::class; } } diff --git a/core/modules/user/user.install b/core/modules/user/user.install index 73fb2947aa3e..35ff4fe9d0e2 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -100,6 +100,7 @@ function user_requirements($phase): array { if ($phase !== 'runtime') { return []; } + $return = []; $result = (bool) \Drupal::entityQuery('user') ->accessCheck(FALSE) @@ -108,17 +109,32 @@ function user_requirements($phase): array { ->execute(); if ($result === FALSE) { - return [ - 'anonymous user' => [ - 'title' => t('Anonymous user'), - 'description' => t('The anonymous user does not exist. See the <a href=":url">restore the anonymous (user ID 0) user record</a> for more information', [ - ':url' => 'https://www.drupal.org/node/1029506', - ]), - 'severity' => REQUIREMENT_WARNING, - ], + $return['anonymous user'] = [ + 'title' => t('Anonymous user'), + 'description' => t('The anonymous user does not exist. See the <a href=":url">restore the anonymous (user ID 0) user record</a> for more information', [ + ':url' => 'https://www.drupal.org/node/1029506', + ]), + 'severity' => REQUIREMENT_WARNING, + ]; + } + + $query = \Drupal::database()->select('users_field_data'); + $query->addExpression('LOWER(mail)', 'lower_mail'); + $query->groupBy('lower_mail'); + $query->having('COUNT(uid) > :matches', [':matches' => 1]); + $conflicts = $query->countQuery()->execute()->fetchField(); + + if ($conflicts > 0) { + $return['conflicting emails'] = [ + 'title' => t('Conflicting user emails'), + 'description' => t('Some user accounts have email addresses that differ only by case. For example, one account might have alice@example.com and another might have Alice@Example.com. See <a href=":url">Conflicting User Emails</a> for more information.', [ + ':url' => 'https://www.drupal.org/node/3486109', + ]), + 'severity' => REQUIREMENT_WARNING, ]; } - return []; + + return $return; } /** -- GitLab