diff --git a/docs/strength-validation.md b/docs/strength-validation.md index 2252797..ef2eed3 100644 --- a/docs/strength-validation.md +++ b/docs/strength-validation.md @@ -7,9 +7,10 @@ constraint with the following options. * message: The validation message (default: password_too_weak) * minLength: Minimum length of the password, should be at least 6 (or 8 for better security) * minStrength: Minimum required strength of the password. +* unicodeEquality: Consider characters from other scripts (unicode) as equal. + When set to false (default) `²` will seen as a special character rather then then 2 in another script. -The strength is computed from various measures including -length and usage of (special) characters. +The strength is computed from various measures including length and usage of (special) characters. **Note:** A strength is measured by the presence of a character and total length. One can have a 'medium' password consisting of only a-z and A-Z, but with a length higher than 12 characters. diff --git a/src/Validator/Constraints/PasswordStrength.php b/src/Validator/Constraints/PasswordStrength.php index ccc9d01..d6f30fd 100644 --- a/src/Validator/Constraints/PasswordStrength.php +++ b/src/Validator/Constraints/PasswordStrength.php @@ -22,6 +22,7 @@ class PasswordStrength extends Constraint public $message = 'password_too_weak'; public $minLength = 6; public $minStrength; + public $unicodeEquality = false; /** * {@inheritdoc} diff --git a/src/Validator/Constraints/PasswordStrengthValidator.php b/src/Validator/Constraints/PasswordStrengthValidator.php index 88bf662..d2d6436 100644 --- a/src/Validator/Constraints/PasswordStrengthValidator.php +++ b/src/Validator/Constraints/PasswordStrengthValidator.php @@ -85,8 +85,6 @@ public function validate($password, Constraint $constraint) } $password = (string) $password; - - $passwordStrength = 0; $passLength = mb_strlen($password); if ($passLength < $constraint->minLength) { @@ -103,30 +101,10 @@ public function validate($password, Constraint $constraint) $tips = array(); - if (preg_match('/[a-zA-Z]/', $password)) { - ++$passwordStrength; - - if (!preg_match('/[a-z]/', $password)) { - $tips[] = 'lowercase_letters'; - } elseif (preg_match('/[A-Z]/', $password)) { - ++$passwordStrength; - } else { - $tips[] = 'uppercase_letters'; - } - } else { - $tips[] = 'letters'; - } - - if (preg_match('/\d+/', $password)) { - ++$passwordStrength; + if ($constraint->unicodeEquality) { + $passwordStrength = $this->calculateStrengthUnicode($password, $tips); } else { - $tips[] = 'numbers'; - } - - if (preg_match('/[^a-zA-Z0-9]/', $password)) { - ++$passwordStrength; - } else { - $tips[] = 'special_chars'; + $passwordStrength = $this->calculateStrength($password, $tips); } if ($passLength > 12) { @@ -162,4 +140,70 @@ public function translateTips($tip) { return $this->translator->trans('rollerworks_password.tip.'.$tip, array(), 'validators'); } + + private function calculateStrength($password, &$tips) + { + $passwordStrength = 0; + + if (preg_match('/[a-zA-Z]/', $password)) { + ++$passwordStrength; + + if (!preg_match('/[a-z]/', $password)) { + $tips[] = 'lowercase_letters'; + } elseif (preg_match('/[A-Z]/', $password)) { + ++$passwordStrength; + } else { + $tips[] = 'uppercase_letters'; + } + } else { + $tips[] = 'letters'; + } + + if (preg_match('/\d+/', $password)) { + ++$passwordStrength; + } else { + $tips[] = 'numbers'; + } + + if (preg_match('/[^a-zA-Z0-9]/', $password)) { + ++$passwordStrength; + } else { + $tips[] = 'special_chars'; + } + + return $passwordStrength; + } + + private function calculateStrengthUnicode($password, &$tips) + { + $passwordStrength = 0; + + if (preg_match('/\p{L}/u', $password)) { + ++$passwordStrength; + + if (!preg_match('/\p{Ll}/u', $password)) { + $tips[] = 'lowercase_letters'; + } elseif (preg_match('/\p{Lu}/u', $password)) { + ++$passwordStrength; + } else { + $tips[] = 'uppercase_letters'; + } + } else { + $tips[] = 'letters'; + } + + if (preg_match('/\p{N}/u', $password)) { + ++$passwordStrength; + } else { + $tips[] = 'numbers'; + } + + if (preg_match('/[^\p{L}\p{N}]/u', $password)) { + ++$passwordStrength; + } else { + $tips[] = 'special_chars'; + } + + return $passwordStrength; + } } diff --git a/tests/Validator/PasswordStrengthTest.php b/tests/Validator/PasswordStrengthTest.php index d9b8d01..9858b90 100644 --- a/tests/Validator/PasswordStrengthTest.php +++ b/tests/Validator/PasswordStrengthTest.php @@ -110,6 +110,40 @@ public function getWeakPasswords() ); } + public function getWeakPasswordsUnicode() + { + $pre = 'rollerworks_password.tip.'; + + // \u{FD3E} = ﴾ = Arabic ornate left parenthesis + + return array( + // Very weak + array(2, 'weaker', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"), + array(2, '123456', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"), + array(2, '²²²²²²', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"), + array(2, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"), + array(2, 'ömgwat', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"), + array(2, '!.!.!.', 1, "{$pre}letters, {$pre}numbers, {$pre}length"), + array(2, '!.!.!﴾', 1, "{$pre}letters, {$pre}numbers, {$pre}length"), + + // Weak + array(3, 'wee6eak', 2, "{$pre}uppercase_letters, {$pre}special_chars, {$pre}length"), + array(3, 'foobar!', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}length"), + array(3, 'Foobar', 2, "{$pre}numbers, {$pre}special_chars, {$pre}length"), + array(3, '123456!', 2, "{$pre}letters, {$pre}length"), + array(3, '7857375923752947', 2, "{$pre}letters, {$pre}special_chars"), + array(3, 'FSDFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"), + array(3, 'FÜKFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"), + array(3, 'fjsfjdljfsjsjjlsj', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars"), + + // Medium + array(4, 'Foobar﴾', 3, "{$pre}numbers, {$pre}length"), + array(4, 'foo-b0r!', 3, "{$pre}uppercase_letters, {$pre}length"), + array(4, 'fjsfjdljfsjsjjls1', 3, "{$pre}uppercase_letters, {$pre}special_chars"), + array(4, '785737592375294b', 3, "{$pre}uppercase_letters, {$pre}special_chars"), + ); + } + public static function getStrongPasswords() { return array( @@ -179,6 +213,27 @@ public function testWeakPasswordsWillNotPass($minStrength, $value, $currentStren ->assertRaised(); } + /** + * @dataProvider getWeakPasswordsUnicode + */ + public function testWeakPasswordsWithUnicodeWillNotPass($minStrength, $value, $currentStrength, $tips = '') + { + $constraint = new PasswordStrength(array('minStrength' => $minStrength, 'minLength' => 6, 'unicodeEquality' => true)); + + $this->validator->validate($value, $constraint); + + $parameters = array( + '{{ length }}' => 6, + '{{ min_strength }}' => 'rollerworks_password.strength_level.'.self::$levelToLabel[$minStrength], + '{{ current_strength }}' => 'rollerworks_password.strength_level.'.self::$levelToLabel[$currentStrength], + '{{ strength_tips }}' => $tips, + ); + + $this->buildViolation('password_too_weak') + ->setParameters($parameters) + ->assertRaised(); + } + /** * @dataProvider getVeryStrongPasswords */