diff --git a/src/Symfony/Component/Validator/Constraints/PasswordStrengthValidator.php b/src/Symfony/Component/Validator/Constraints/PasswordStrengthValidator.php index 96a4a74f7287a..09f2004287693 100644 --- a/src/Symfony/Component/Validator/Constraints/PasswordStrengthValidator.php +++ b/src/Symfony/Component/Validator/Constraints/PasswordStrengthValidator.php @@ -15,14 +15,12 @@ use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; +use Symfony\Component\Validator\Password\PasswordStrengthEstimatorInterface; final class PasswordStrengthValidator extends ConstraintValidator { - /** - * @param (\Closure(string):PasswordStrength::STRENGTH_*)|null $passwordStrengthEstimator - */ public function __construct( - private readonly ?\Closure $passwordStrengthEstimator = null, + private readonly PasswordStrengthEstimatorInterface $passwordStrengthEstimator ) { } @@ -39,8 +37,8 @@ public function validate(#[\SensitiveParameter] mixed $value, Constraint $constr if (!\is_string($value) && !$value instanceof \Stringable) { throw new UnexpectedValueException($value, 'string'); } - $passwordStrengthEstimator = $this->passwordStrengthEstimator ?? self::estimateStrength(...); - $strength = $passwordStrengthEstimator((string) $value); + + $strength = $this->passwordStrengthEstimator->estimateStrength((string) $value); if ($strength < $constraint->minScore) { $this->context->buildViolation($constraint->message) @@ -49,43 +47,4 @@ public function validate(#[\SensitiveParameter] mixed $value, Constraint $constr ->addViolation(); } } - - /** - * Returns the estimated strength of a password. - * - * The higher the value, the stronger the password. - * - * @return PasswordStrength::STRENGTH_* - */ - private static function estimateStrength(#[\SensitiveParameter] string $password): int - { - if (!$length = \strlen($password)) { - return PasswordStrength::STRENGTH_VERY_WEAK; - } - $password = count_chars($password, 1); - $chars = \count($password); - - $control = $digit = $upper = $lower = $symbol = $other = 0; - foreach ($password as $chr => $count) { - match (true) { - $chr < 32 || 127 === $chr => $control = 33, - 48 <= $chr && $chr <= 57 => $digit = 10, - 65 <= $chr && $chr <= 90 => $upper = 26, - 97 <= $chr && $chr <= 122 => $lower = 26, - 128 <= $chr => $other = 128, - default => $symbol = 33, - }; - } - - $pool = $lower + $upper + $digit + $symbol + $control + $other; - $entropy = $chars * log($pool, 2) + ($length - $chars) * log($chars, 2); - - return match (true) { - $entropy >= 120 => PasswordStrength::STRENGTH_VERY_STRONG, - $entropy >= 100 => PasswordStrength::STRENGTH_STRONG, - $entropy >= 80 => PasswordStrength::STRENGTH_MEDIUM, - $entropy >= 60 => PasswordStrength::STRENGTH_WEAK, - default => PasswordStrength::STRENGTH_VERY_WEAK, - }; - } } diff --git a/src/Symfony/Component/Validator/Password/PasswordStrengthEstimator.php b/src/Symfony/Component/Validator/Password/PasswordStrengthEstimator.php new file mode 100644 index 0000000000000..04de6e3fddb67 --- /dev/null +++ b/src/Symfony/Component/Validator/Password/PasswordStrengthEstimator.php @@ -0,0 +1,41 @@ + $count) { + match (true) { + $chr < 32 || 127 === $chr => $control = 33, + 48 <= $chr && $chr <= 57 => $digit = 10, + 65 <= $chr && $chr <= 90 => $upper = 26, + 97 <= $chr && $chr <= 122 => $lower = 26, + 128 <= $chr => $other = 128, + default => $symbol = 33, + }; + } + + $pool = $lower + $upper + $digit + $symbol + $control + $other; + $entropy = $chars * log($pool, 2) + ($length - $chars) * log($chars, 2); + + return match (true) { + $entropy >= 120 => PasswordStrength::STRENGTH_VERY_STRONG, + $entropy >= 100 => PasswordStrength::STRENGTH_STRONG, + $entropy >= 80 => PasswordStrength::STRENGTH_MEDIUM, + $entropy >= 60 => PasswordStrength::STRENGTH_WEAK, + default => PasswordStrength::STRENGTH_VERY_WEAK, + }; + } +} diff --git a/src/Symfony/Component/Validator/Password/PasswordStrengthEstimatorInterface.php b/src/Symfony/Component/Validator/Password/PasswordStrengthEstimatorInterface.php new file mode 100644 index 0000000000000..22b59c77166bf --- /dev/null +++ b/src/Symfony/Component/Validator/Password/PasswordStrengthEstimatorInterface.php @@ -0,0 +1,18 @@ +estimateStrength($password)); + } + + /** @return array> */ + public function getPasswords(): iterable + { + yield ['How-is-this', PasswordStrength::STRENGTH_WEAK]; + yield ['Reasonable-pwd', PasswordStrength::STRENGTH_MEDIUM]; + yield ['This 1s a very g00d Pa55word! ;-)', PasswordStrength::STRENGTH_VERY_STRONG]; + yield ['pudding-smack-👌🏼-fox-😎', PasswordStrength::STRENGTH_VERY_STRONG]; + yield [new StringableValue('How-is-this'), PasswordStrength::STRENGTH_WEAK]; + } +}