diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index ea5b271..b161d7c 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -3,10 +3,10 @@ name: 'CI'
on:
push:
branches:
- - main
+ - '2.0'
pull_request:
branches:
- - main
+ - '2.0'
jobs:
cs-fixer:
@@ -22,7 +22,7 @@ jobs:
steps:
-
name: 'Check out'
- uses: 'actions/checkout@v2'
+ uses: 'actions/checkout@v6'
-
name: 'Set up PHP'
@@ -38,7 +38,7 @@ jobs:
-
name: 'Cache dependencies'
- uses: 'actions/cache@v2'
+ uses: 'actions/cache@v4'
with:
path: '${{ steps.composer-cache.outputs.cache-dir }}'
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
@@ -52,49 +52,6 @@ jobs:
name: 'Check the code style'
run: 'make cs-full'
- phpstan:
- name: 'PhpStan'
-
- runs-on: 'ubuntu-latest'
-
- strategy:
- matrix:
- php-version:
- - '8.2'
-
- steps:
- -
- name: 'Check out'
- uses: 'actions/checkout@v2'
-
- -
- name: 'Set up PHP'
- uses: 'shivammathur/setup-php@v2'
- with:
- php-version: '${{ matrix.php-version }}'
- coverage: 'none'
-
- -
- name: 'Get Composer cache directory'
- id: 'composer-cache'
- run: 'echo "::set-output name=cache-dir::$(composer config cache-files-dir)"'
-
- -
- name: 'Cache dependencies'
- uses: 'actions/cache@v2'
- with:
- path: '${{ steps.composer-cache.outputs.cache-dir }}'
- key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
- restore-keys: 'php-${{ matrix.php-version }}-composer-locked-'
-
- -
- name: 'Install dependencies'
- run: 'composer update --no-progress --prefer-stable'
-
- -
- name: 'Run PhpStan'
- run: 'vendor/bin/phpstan analyze --no-progress'
-
tests:
name: 'PHPUnit'
@@ -106,21 +63,22 @@ jobs:
-
php-version: '8.2'
composer-options: '--prefer-stable'
- symfony-version: '6.3'
+ symfony-version: '^6.4'
+
-
php-version: '8.2'
composer-options: '--prefer-stable'
- symfony-version: '^6.4'
+ symfony-version: '^7.4'
-
- php-version: '8.2'
+ php-version: '8.4'
composer-options: '--prefer-stable'
- symfony-version: '^7.0'
+ symfony-version: '^8.0'
steps:
-
name: 'Check out'
- uses: 'actions/checkout@v2'
+ uses: 'actions/checkout@v6'
-
name: 'Set up PHP'
@@ -136,7 +94,7 @@ jobs:
-
name: 'Cache dependencies'
- uses: 'actions/cache@v2'
+ uses: 'actions/cache@v4'
with:
path: '${{ steps.composer-cache.outputs.cache-dir }}'
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
diff --git a/.gitignore b/.gitignore
index 32cb520..453f870 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
symfony.lock
.phpunit.result.cache
.php-cs-fixer.cache
+.phpunit.cache/
diff --git a/composer.json b/composer.json
index 334e623..6ab8c68 100644
--- a/composer.json
+++ b/composer.json
@@ -19,18 +19,18 @@
}
],
"require": {
- "php": ">=8.2",
- "symfony/config": "^6.0 || ^7.0",
+ "php": "^8.2",
+ "symfony/config": "^6.4 || ^7.4 || ^8.0",
"symfony/polyfill-mbstring": "^1.5.0",
- "symfony/translation": "^6.0 || ^7.0",
- "symfony/validator": "^6.0 || ^7.0"
+ "symfony/translation": "^6.4 || ^7.4 || ^8.0",
+ "symfony/validator": "^6.4 || ^7.4 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-symfony": "^1.2",
"phpunit/phpunit": "^9.5",
- "symfony/phpunit-bridge": "^6.0 || ^7.0"
+ "symfony/phpunit-bridge": "^7.4 || ^8.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index b81e113..811c285 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -17,7 +17,7 @@
-
+
diff --git a/src/Validator/Constraints/ConstraintCompatTrait.php b/src/Validator/Constraints/ConstraintCompatTrait.php
new file mode 100644
index 0000000..bc1ecb9
--- /dev/null
+++ b/src/Validator/Constraints/ConstraintCompatTrait.php
@@ -0,0 +1,154 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Rollerworks\Component\PasswordStrength\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\InvalidOptionsException;
+use Symfony\Component\Validator\Exception\MissingOptionsException;
+
+if (method_exists(Constraint::class, 'normalizeOptions')) {
+ /**
+ * @internal
+ */
+ trait ConstraintCompatTrait
+ {
+ protected function initOptions(?array $options, ?array $groups, mixed $payload): void
+ {
+ // Noop
+ }
+ }
+} else {
+
+ /**
+ * @internal
+ */
+ trait ConstraintCompatTrait
+ {
+ protected function initOptions(?array $options, ?array $groups, mixed $payload): void
+ {
+ if ($options === null) {
+ return;
+ }
+
+ trigger_deprecation('symfony/validator', '7.4', 'Support for evaluating options in the %1$s class is deprecated. Initialize properties in the constructor of %1$s instead.', static::class);
+
+ $options = $this->normalizeOptions($options);
+
+ if ($groups !== null) {
+ $options['groups'] = $groups;
+ }
+ $options['payload'] = $payload ?? $options['payload'] ?? null;
+
+ foreach ($options as $name => $value) {
+ $this->{$name} = $value;
+ }
+ }
+
+ /**
+ * @deprecated since Symfony 7.4
+ *
+ * @return array
+ */
+ protected function normalizeOptions(mixed $options): array
+ {
+ $normalizedOptions = [];
+ $defaultOption = $this->getDefaultOption(false);
+ $invalidOptions = [];
+ $missingOptions = array_flip($this->getRequiredOptions(false));
+ $knownOptions = get_class_vars(static::class);
+
+ if (\is_array($options) && isset($options['value']) && ! property_exists($this, 'value')) {
+ if ($defaultOption === null) {
+ throw new ConstraintDefinitionException(\sprintf('No default option is configured for constraint "%s".', static::class));
+ }
+
+ $options[$defaultOption] = $options['value'];
+ unset($options['value']);
+ }
+
+ if (\is_array($options)) {
+ reset($options);
+ }
+
+ if ($options && \is_array($options) && \is_string(key($options))) {
+ foreach ($options as $option => $value) {
+ if (\array_key_exists($option, $knownOptions)) {
+ $normalizedOptions[$option] = $value;
+ unset($missingOptions[$option]);
+ } else {
+ $invalidOptions[] = $option;
+ }
+ }
+ } elseif ($options !== null && ! (\is_array($options) && \count($options) === 0)) {
+ if ($defaultOption === null) {
+ throw new ConstraintDefinitionException(\sprintf('No default option is configured for constraint "%s".', static::class));
+ }
+
+ if (\array_key_exists($defaultOption, $knownOptions)) {
+ $normalizedOptions[$defaultOption] = $options;
+ unset($missingOptions[$defaultOption]);
+ } else {
+ $invalidOptions[] = $defaultOption;
+ }
+ }
+
+ if (\count($invalidOptions) > 0) {
+ throw new InvalidOptionsException(\sprintf('The options "%s" do not exist in constraint "%s".', implode('", "', $invalidOptions), static::class), $invalidOptions);
+ }
+
+ if (\count($missingOptions) > 0) {
+ throw new MissingOptionsException(\sprintf('The options "%s" must be set for constraint "%s".', implode('", "', array_keys($missingOptions)), static::class), array_keys($missingOptions));
+ }
+
+ return $normalizedOptions;
+ }
+
+ /**
+ * Returns the name of the default option.
+ *
+ * Override this method to define a default option.
+ *
+ * @deprecated since Symfony 7.4
+ * @see __construct()
+ */
+ public function getDefaultOption(): ?string
+ {
+ if (\func_num_args() === 0 || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the name of the required options.
+ *
+ * Override this method if you want to define required options.
+ *
+ * @return string[]
+ *
+ * @deprecated since Symfony 7.4
+ * @see __construct()
+ */
+ public function getRequiredOptions(): array
+ {
+ if (\func_num_args() === 0 || func_get_arg(0)) {
+ trigger_deprecation('symfony/validator', '7.4', 'The %s() method is deprecated.', __METHOD__);
+ }
+
+ return [];
+ }
+ }
+}
diff --git a/src/Validator/Constraints/PasswordRequirements.php b/src/Validator/Constraints/PasswordRequirements.php
index 0e89c5f..9ff78f7 100644
--- a/src/Validator/Constraints/PasswordRequirements.php
+++ b/src/Validator/Constraints/PasswordRequirements.php
@@ -15,12 +15,13 @@
/**
* @Annotation
- *
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class PasswordRequirements extends Constraint
{
+ use ConstraintCompatTrait;
+
public string $tooShortMessage = 'Your password must be at least {{length}} characters long.';
public string $missingLettersMessage = 'Your password must include at least one letter.';
public string $requireCaseDiffMessage = 'Your password must include both upper and lower case letters.';
@@ -49,6 +50,7 @@ public function __construct(
?string $missingSpecialCharacterMessage = null
) {
parent::__construct($options ?? [], $groups, $payload);
+ $this->initOptions($options, $groups, $payload);
$this->tooShortMessage = $tooShortMessage ?? $this->tooShortMessage;
$this->missingLettersMessage = $missingLettersMessage ?? $this->missingLettersMessage;
diff --git a/src/Validator/Constraints/PasswordStrength.php b/src/Validator/Constraints/PasswordStrength.php
index d4c364b..ccbc853 100644
--- a/src/Validator/Constraints/PasswordStrength.php
+++ b/src/Validator/Constraints/PasswordStrength.php
@@ -15,12 +15,13 @@
/**
* @Annotation
- *
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class PasswordStrength extends Constraint
{
+ use ConstraintCompatTrait;
+
public string $tooShortMessage = 'Your password must be at least {{length}} characters long.';
public string $message = 'password_too_weak';
public int $minLength = 6;
@@ -51,6 +52,7 @@ public function __construct(
}
parent::__construct($finalOptions, $groups, $payload);
+ $this->initOptions($finalOptions, $groups, $payload);
$this->minLength = $minLength ?? $this->minLength;
$this->unicodeEquality = $unicodeEquality ?? $this->unicodeEquality;
diff --git a/tests/Validator/PasswordRequirementsValidatorTest.php b/tests/Validator/PasswordRequirementsValidatorTest.php
index 9e0eb40..5720399 100644
--- a/tests/Validator/PasswordRequirementsValidatorTest.php
+++ b/tests/Validator/PasswordRequirementsValidatorTest.php
@@ -46,7 +46,7 @@ public function empty_is_valid(): void
}
/**
- * @dataProvider provideValid_value_constraintsCases
+ * @dataProvider provideValid_value_constraintsCasesLegacy
*
* @test
*/
@@ -59,14 +59,39 @@ public function valid_value_constraints(string $value, PasswordRequirements $con
$this->assertNoViolation();
}
+ /**
+ * @return iterable
+ */
+ public static function provideValid_value_constraintsCasesLegacy(): iterable
+ {
+ return [
+ ['test', new PasswordRequirements(['minLength' => 3])],
+ ['1234567', new PasswordRequirements(['requireLetters' => false])],
+ ['1234567', new PasswordRequirements(['requireLetters' => false])],
+ ['aBcDez', new PasswordRequirements(['requireCaseDiff' => true])],
+ ['abcdef', new PasswordRequirements(['requireNumbers' => false])],
+ ['123456', new PasswordRequirements(['requireLetters' => false, 'requireNumbers' => true])],
+ ['123456789', new PasswordRequirements(['requireLetters' => false, 'requireNumbers' => true])],
+ ['abcd12345', new PasswordRequirements(['requireLetters' => true, 'requireNumbers' => true])],
+ ['1234abc56789', new PasswordRequirements(['requireLetters' => true, 'requireNumbers' => true])],
+
+ ['®', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ['»', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ['<>', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ['{}', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ];
+ }
+
/**
* @dataProvider provideViolation_value_constraintsCases
*
* @test
*
+ * @group legacy
+ *
* @param array}> $violations
*/
- public function violation_value_constraints(string $value, PasswordRequirements $constraint, array $violations = []): void
+ public function violation_value_constraints_legacy(string $value, PasswordRequirements $constraint, array $violations = []): void
{
$this->value = $value;
/** @var ConstraintViolationAssertion $constraintViolationAssertion */
@@ -97,26 +122,40 @@ public function violation_value_constraints(string $value, PasswordRequirements
}
/**
- * @return iterable
+ * @dataProvider provideViolation_value_constraintsCases
+ *
+ * @test
+ *
+ * @param array}> $violations
*/
- public static function provideValid_value_constraintsCases(): iterable
+ public function violation_value_constraints(string $value, PasswordRequirements $constraint, array $violations = []): void
{
- return [
- ['test', new PasswordRequirements(['minLength' => 3])],
- ['1234567', new PasswordRequirements(['requireLetters' => false])],
- ['1234567', new PasswordRequirements(['requireLetters' => false])],
- ['aBcDez', new PasswordRequirements(['requireCaseDiff' => true])],
- ['abcdef', new PasswordRequirements(['requireNumbers' => false])],
- ['123456', new PasswordRequirements(['requireLetters' => false, 'requireNumbers' => true])],
- ['123456789', new PasswordRequirements(['requireLetters' => false, 'requireNumbers' => true])],
- ['abcd12345', new PasswordRequirements(['requireLetters' => true, 'requireNumbers' => true])],
- ['1234abc56789', new PasswordRequirements(['requireLetters' => true, 'requireNumbers' => true])],
+ $this->value = $value;
+ /** @var ConstraintViolationAssertion $constraintViolationAssertion */
+ $constraintViolationAssertion = null; // Shut-up PHPStan
- ['®', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
- ['»', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
- ['<>', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
- ['{}', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
- ];
+ $this->validator->validate($value, $constraint);
+
+ /**
+ * @var array $violation
+ */
+ foreach ($violations as $i => $violation) {
+ if ($i === 0) {
+ $constraintViolationAssertion = $this->buildViolation($violation[0])
+ ->setParameters($violation[1] ?? [])
+ ->setInvalidValue($value)
+ ;
+ } else {
+ $constraintViolationAssertion = $constraintViolationAssertion->buildNextViolation($violation[0])
+ ->setParameters($violation[1] ?? [])
+ ->setInvalidValue($value)
+ ;
+ }
+
+ if ($i == \count($violations) - 1) {
+ $constraintViolationAssertion->assertRaised();
+ }
+ }
}
/**
@@ -127,25 +166,48 @@ public static function provideViolation_value_constraintsCases(): iterable
$constraint = new PasswordRequirements();
return [
- ['1', new PasswordRequirements(['minLength' => 2, 'requireLetters' => false]), [
+ ['1', new PasswordRequirements(minLength: 2, requireLetters: false), [
[$constraint->tooShortMessage, ['{{length}}' => 2]],
]],
- ['test', new PasswordRequirements(['requireLetters' => true]), [
+ ['test', new PasswordRequirements(requireLetters: true), [
[$constraint->tooShortMessage, ['{{length}}' => $constraint->minLength]],
]],
- ['123456', new PasswordRequirements(['requireLetters' => true]), [
+ ['123456', new PasswordRequirements(requireLetters: true), [
[$constraint->missingLettersMessage],
]],
- ['abcdez', new PasswordRequirements(['requireCaseDiff' => true]), [
+ ['abcdez', new PasswordRequirements(requireCaseDiff: true), [
[$constraint->requireCaseDiffMessage],
]],
- ['!@#$%^&*()-', new PasswordRequirements(['requireLetters' => true, 'requireNumbers' => true]), [
+ ['!@#$%^&*()-', new PasswordRequirements(requireLetters: true, requireNumbers: true), [
[$constraint->missingLettersMessage],
[$constraint->missingNumbersMessage],
]],
- ['aerfghy', new PasswordRequirements(['requireLetters' => false, 'requireSpecialCharacter' => true]), [
+ ['aerfghy', new PasswordRequirements(requireLetters: false, requireSpecialCharacter: true), [
[$constraint->missingSpecialCharacterMessage],
]],
];
}
+
+ /**
+ * @return iterable
+ */
+ public static function provideValid_value_constraintsCases(): iterable
+ {
+ return [
+ ['test', new PasswordRequirements(['minLength' => 3])],
+ ['1234567', new PasswordRequirements(['requireLetters' => false])],
+ ['1234567', new PasswordRequirements(['requireLetters' => false])],
+ ['aBcDez', new PasswordRequirements(['requireCaseDiff' => true])],
+ ['abcdef', new PasswordRequirements(['requireNumbers' => false])],
+ ['123456', new PasswordRequirements(['requireLetters' => false, 'requireNumbers' => true])],
+ ['123456789', new PasswordRequirements(['requireLetters' => false, 'requireNumbers' => true])],
+ ['abcd12345', new PasswordRequirements(['requireLetters' => true, 'requireNumbers' => true])],
+ ['1234abc56789', new PasswordRequirements(['requireLetters' => true, 'requireNumbers' => true])],
+
+ ['®', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ['»', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ['<>', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ['{}', new PasswordRequirements(['minLength' => 1, 'requireLetters' => false, 'requireSpecialCharacter' => true])],
+ ];
+ }
}
diff --git a/tests/Validator/PasswordStrengthLegacyTest.php b/tests/Validator/PasswordStrengthLegacyTest.php
new file mode 100644
index 0000000..ac9578a
--- /dev/null
+++ b/tests/Validator/PasswordStrengthLegacyTest.php
@@ -0,0 +1,304 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Rollerworks\Component\PasswordStrength\Tests\Validator;
+
+use Rollerworks\Component\PasswordStrength\Validator\Constraints\PasswordStrength;
+use Rollerworks\Component\PasswordStrength\Validator\Constraints\PasswordStrengthValidator;
+use Symfony\Component\Translation\Translator;
+use Symfony\Component\Validator\ConstraintValidatorInterface;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
+
+/**
+ * @internal
+ *
+ * @template-extends ConstraintValidatorTestCase
+ *
+ * @group legacy
+ */
+final class PasswordStrengthLegacyTest extends ConstraintValidatorTestCase
+{
+ /**
+ * @var array
+ */
+ private static $levelToLabel = [
+ 1 => 'very_weak',
+ 2 => 'weak',
+ 3 => 'medium',
+ 4 => 'strong',
+ 5 => 'very_strong',
+ ];
+
+ protected function createValidator(): ConstraintValidatorInterface
+ {
+ return new PasswordStrengthValidator(new Translator('en'));
+ }
+
+ /** @test */
+ public function constraints_options_are_properly_resolved(): void
+ {
+ // Default option
+ $constraint = new PasswordStrength(3);
+ self::assertEquals(3, $constraint->minStrength);
+
+ // By option
+ $constraint = new PasswordStrength(['minStrength' => 3]);
+ self::assertEquals(3, $constraint->minStrength);
+
+ // Specific argument
+ $constraint = new PasswordStrength(null, null, null, 3);
+ self::assertEquals(3, $constraint->minStrength);
+ }
+
+ /** @test */
+ public function null_is_valid(): void
+ {
+ $this->validator->validate(null, new PasswordStrength(6));
+
+ $this->assertNoViolation();
+ }
+
+ /** @test */
+ public function empty_is_valid(): void
+ {
+ $this->validator->validate('', new PasswordStrength(6));
+
+ $this->assertNoViolation();
+ }
+
+ /** @test */
+ public function expects_string_compatible_type(): void
+ {
+ $this->expectException(UnexpectedTypeException::class);
+
+ $this->validator->validate(new \stdClass(), new PasswordStrength(5));
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideStrongPasswords(): iterable
+ {
+ return [
+ ['Foobar!55!'],
+ ['Foobar$55'],
+ ['Foobar€55'],
+ ['Foobar€55'],
+ ];
+ }
+
+ /** @test */
+ public function short_password_will_not_pass(): void
+ {
+ $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 6]);
+
+ $this->validator->validate('foo', $constraint);
+
+ $parameters = [
+ '{{length}}' => 6,
+ ];
+
+ $this->buildViolation('Your password must be at least {{length}} characters long.')
+ ->setParameters($parameters)
+ ->assertRaised()
+ ;
+ }
+
+ /** @test */
+ public function short_password_in_multi_byte_will_not_pass(): void
+ {
+ $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 7]);
+
+ $this->validator->validate('foöled', $constraint);
+
+ $parameters = [
+ '{{length}}' => 7,
+ ];
+
+ $this->buildViolation('Your password must be at least {{length}} characters long.')
+ ->setParameters($parameters)
+ ->assertRaised()
+ ;
+ }
+
+ /**
+ * @dataProvider provideWeak_passwords_will_not_passCases
+ *
+ * @test
+ */
+ public function weak_passwords_will_not_pass(int $minStrength, string $value, int $currentStrength, string $tips = ''): void
+ {
+ $constraint = new PasswordStrength(['minStrength' => $minStrength, 'minLength' => 6]);
+
+ $this->validator->validate($value, $constraint);
+
+ $parameters = [
+ '{{ 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()
+ ;
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideWeak_passwords_will_not_passCases(): iterable
+ {
+ $pre = 'rollerworks_password.tip.';
+
+ return [
+ // Very weak
+ [2, 'weaker', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '123456', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
+ [2, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '!.!.!.', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
+
+ // Weak
+ [3, 'wee6eak', 2, "{$pre}uppercase_letters, {$pre}special_chars, {$pre}length"],
+ [3, 'foobar!', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}length"],
+ [3, 'Foobar', 2, "{$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [3, '123456!', 2, "{$pre}letters, {$pre}length"],
+ [3, '7857375923752947', 2, "{$pre}letters, {$pre}special_chars"],
+ [3, 'FSDFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
+ [3, 'fjsfjdljfsjsjjlsj', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars"],
+
+ // Medium
+ [4, 'Foobar!', 3, "{$pre}numbers, {$pre}length"],
+ [4, 'foo-b0r!', 3, "{$pre}uppercase_letters, {$pre}length"],
+ [4, 'fjsfjdljfsjsjjls1', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ [4, '785737592375294b', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ ];
+ }
+
+ /**
+ * @dataProvider provideWeak_passwords_with_unicode_will_not_passCases
+ *
+ * @test
+ */
+ public function weak_passwords_with_unicode_will_not_pass(int $minStrength, string $value, int $currentStrength, string $tips = ''): void
+ {
+ $constraint = new PasswordStrength(['minStrength' => $minStrength, 'minLength' => 6, 'unicodeEquality' => true]);
+
+ $this->validator->validate($value, $constraint);
+
+ $parameters = [
+ '{{ 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()
+ ;
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideWeak_passwords_with_unicode_will_not_passCases(): iterable
+ {
+ $pre = 'rollerworks_password.tip.';
+
+ // \u{FD3E} = ﴾ = Arabic ornate left parenthesis
+
+ return [
+ // Very weak
+ [2, 'weaker', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '123456', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
+ [2, '²²²²²²', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
+ [2, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, 'ömgwat', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '!.!.!.', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
+ [2, '!.!.!﴾', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
+
+ // Weak
+ [3, 'wee6eak', 2, "{$pre}uppercase_letters, {$pre}special_chars, {$pre}length"],
+ [3, 'foobar!', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}length"],
+ [3, 'Foobar', 2, "{$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [3, '123456!', 2, "{$pre}letters, {$pre}length"],
+ [3, '7857375923752947', 2, "{$pre}letters, {$pre}special_chars"],
+ [3, 'FSDFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
+ [3, 'FÜKFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
+ [3, 'fjsfjdljfsjsjjlsj', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars"],
+
+ // Medium
+ [4, 'Foobar﴾', 3, "{$pre}numbers, {$pre}length"],
+ [4, 'foo-b0r!', 3, "{$pre}uppercase_letters, {$pre}length"],
+ [4, 'fjsfjdljfsjsjjls1', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ [4, '785737592375294b', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ ];
+ }
+
+ /**
+ * @dataProvider provideStrong_passwords_will_passCases
+ *
+ * @test
+ */
+ public function strong_passwords_will_pass(string $value): void
+ {
+ $constraint = new PasswordStrength(5);
+
+ $this->validator->validate($value, $constraint);
+
+ $this->assertNoViolation();
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideStrong_passwords_will_passCases(): iterable
+ {
+ return [
+ ['Foobar$55_4&F'],
+ ['L33RoyJ3Jenkins!'],
+ ];
+ }
+
+ /** @test */
+ public function constraint_get_default_option(): void
+ {
+ $constraint = new PasswordStrength(5);
+
+ self::assertEquals(5, $constraint->minStrength);
+ }
+
+ /** @test */
+ public function parameters_are_translated_when_translator_is_missing(): void
+ {
+ $this->validator = new PasswordStrengthValidator();
+ $this->validator->initialize($this->context);
+
+ $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 6]);
+
+ $this->validator->validate('FD43f.!', $constraint);
+
+ $parameters = [
+ '{{ length }}' => 6,
+ '{{ current_strength }}' => 'Strong',
+ '{{ min_strength }}' => 'Very strong',
+ '{{ strength_tips }}' => 'add more characters',
+ ];
+
+ $this->buildViolation('password_too_weak')
+ ->setParameters($parameters)
+ ->assertRaised()
+ ;
+ }
+}
diff --git a/tests/Validator/PasswordStrengthTest.php b/tests/Validator/PasswordStrengthTest.php
index 1a82025..070a38b 100644
--- a/tests/Validator/PasswordStrengthTest.php
+++ b/tests/Validator/PasswordStrengthTest.php
@@ -41,22 +41,6 @@ protected function createValidator(): ConstraintValidatorInterface
return new PasswordStrengthValidator(new Translator('en'));
}
- /** @test */
- public function constraints_options_are_properly_resolved(): void
- {
- // Default option
- $constraint = new PasswordStrength(3);
- self::assertEquals(3, $constraint->minStrength);
-
- // By option
- $constraint = new PasswordStrength(['minStrength' => 3]);
- self::assertEquals(3, $constraint->minStrength);
-
- // Specific argument
- $constraint = new PasswordStrength(null, null, null, 3);
- self::assertEquals(3, $constraint->minStrength);
- }
-
/** @test */
public function null_is_valid(): void
{
@@ -81,74 +65,6 @@ public function expects_string_compatible_type(): void
$this->validator->validate(new \stdClass(), new PasswordStrength(5));
}
- /**
- * @return iterable
- */
- public function provideWeak_passwords_will_not_passCases(): iterable
- {
- $pre = 'rollerworks_password.tip.';
-
- return [
- // Very weak
- [2, 'weaker', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
- [2, '123456', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
- [2, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
- [2, '!.!.!.', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
-
- // Weak
- [3, 'wee6eak', 2, "{$pre}uppercase_letters, {$pre}special_chars, {$pre}length"],
- [3, 'foobar!', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}length"],
- [3, 'Foobar', 2, "{$pre}numbers, {$pre}special_chars, {$pre}length"],
- [3, '123456!', 2, "{$pre}letters, {$pre}length"],
- [3, '7857375923752947', 2, "{$pre}letters, {$pre}special_chars"],
- [3, 'FSDFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
- [3, 'fjsfjdljfsjsjjlsj', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars"],
-
- // Medium
- [4, 'Foobar!', 3, "{$pre}numbers, {$pre}length"],
- [4, 'foo-b0r!', 3, "{$pre}uppercase_letters, {$pre}length"],
- [4, 'fjsfjdljfsjsjjls1', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
- [4, '785737592375294b', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
- ];
- }
-
- /**
- * @return iterable
- */
- public function provideWeak_passwords_with_unicode_will_not_passCases(): iterable
- {
- $pre = 'rollerworks_password.tip.';
-
- // \u{FD3E} = ﴾ = Arabic ornate left parenthesis
-
- return [
- // Very weak
- [2, 'weaker', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
- [2, '123456', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
- [2, '²²²²²²', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
- [2, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
- [2, 'ömgwat', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
- [2, '!.!.!.', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
- [2, '!.!.!﴾', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
-
- // Weak
- [3, 'wee6eak', 2, "{$pre}uppercase_letters, {$pre}special_chars, {$pre}length"],
- [3, 'foobar!', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}length"],
- [3, 'Foobar', 2, "{$pre}numbers, {$pre}special_chars, {$pre}length"],
- [3, '123456!', 2, "{$pre}letters, {$pre}length"],
- [3, '7857375923752947', 2, "{$pre}letters, {$pre}special_chars"],
- [3, 'FSDFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
- [3, 'FÜKFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
- [3, 'fjsfjdljfsjsjjlsj', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars"],
-
- // Medium
- [4, 'Foobar﴾', 3, "{$pre}numbers, {$pre}length"],
- [4, 'foo-b0r!', 3, "{$pre}uppercase_letters, {$pre}length"],
- [4, 'fjsfjdljfsjsjjls1', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
- [4, '785737592375294b', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
- ];
- }
-
/**
* @return iterable
*/
@@ -162,21 +78,10 @@ public static function provideStrongPasswords(): iterable
];
}
- /**
- * @return iterable
- */
- public static function provideStrong_passwords_will_passCases(): iterable
- {
- return [
- ['Foobar$55_4&F'],
- ['L33RoyJ3Jenkins!'],
- ];
- }
-
/** @test */
public function short_password_will_not_pass(): void
{
- $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 6]);
+ $constraint = new PasswordStrength(minStrength: 5, minLength: 6);
$this->validator->validate('foo', $constraint);
@@ -193,7 +98,7 @@ public function short_password_will_not_pass(): void
/** @test */
public function short_password_in_multi_byte_will_not_pass(): void
{
- $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 7]);
+ $constraint = new PasswordStrength(minStrength: 5, minLength: 7);
$this->validator->validate('foöled', $constraint);
@@ -208,13 +113,13 @@ public function short_password_in_multi_byte_will_not_pass(): void
}
/**
- * @dataProvider provideWeak_passwords_will_not_passCases
- *
* @test
+ *
+ * @dataProvider provideWeak_passwords_will_not_passCases
*/
public function weak_passwords_will_not_pass(int $minStrength, string $value, int $currentStrength, string $tips = ''): void
{
- $constraint = new PasswordStrength(['minStrength' => $minStrength, 'minLength' => 6]);
+ $constraint = new PasswordStrength(minStrength: $minStrength, minLength: 6);
$this->validator->validate($value, $constraint);
@@ -231,6 +136,37 @@ public function weak_passwords_will_not_pass(int $minStrength, string $value, in
;
}
+ /**
+ * @return iterable
+ */
+ public static function provideWeak_passwords_will_not_passCases(): iterable
+ {
+ $pre = 'rollerworks_password.tip.';
+
+ return [
+ // Very weak
+ [2, 'weaker', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '123456', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
+ [2, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '!.!.!.', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
+
+ // Weak
+ [3, 'wee6eak', 2, "{$pre}uppercase_letters, {$pre}special_chars, {$pre}length"],
+ [3, 'foobar!', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}length"],
+ [3, 'Foobar', 2, "{$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [3, '123456!', 2, "{$pre}letters, {$pre}length"],
+ [3, '7857375923752947', 2, "{$pre}letters, {$pre}special_chars"],
+ [3, 'FSDFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
+ [3, 'fjsfjdljfsjsjjlsj', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars"],
+
+ // Medium
+ [4, 'Foobar!', 3, "{$pre}numbers, {$pre}length"],
+ [4, 'foo-b0r!', 3, "{$pre}uppercase_letters, {$pre}length"],
+ [4, 'fjsfjdljfsjsjjls1', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ [4, '785737592375294b', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ ];
+ }
+
/**
* @dataProvider provideWeak_passwords_with_unicode_will_not_passCases
*
@@ -238,7 +174,7 @@ public function weak_passwords_will_not_pass(int $minStrength, string $value, in
*/
public function weak_passwords_with_unicode_will_not_pass(int $minStrength, string $value, int $currentStrength, string $tips = ''): void
{
- $constraint = new PasswordStrength(['minStrength' => $minStrength, 'minLength' => 6, 'unicodeEquality' => true]);
+ $constraint = new PasswordStrength(minStrength: $minStrength, minLength: 6, unicodeEquality: true);
$this->validator->validate($value, $constraint);
@@ -255,6 +191,43 @@ public function weak_passwords_with_unicode_will_not_pass(int $minStrength, stri
;
}
+ /**
+ * @return iterable
+ */
+ public static function provideWeak_passwords_with_unicode_will_not_passCases(): iterable
+ {
+ $pre = 'rollerworks_password.tip.';
+
+ // \u{FD3E} = ﴾ = Arabic ornate left parenthesis
+
+ return [
+ // Very weak
+ [2, 'weaker', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '123456', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
+ [2, '²²²²²²', 1, "{$pre}letters, {$pre}special_chars, {$pre}length"],
+ [2, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, 'ömgwat', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [2, '!.!.!.', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
+ [2, '!.!.!﴾', 1, "{$pre}letters, {$pre}numbers, {$pre}length"],
+
+ // Weak
+ [3, 'wee6eak', 2, "{$pre}uppercase_letters, {$pre}special_chars, {$pre}length"],
+ [3, 'foobar!', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}length"],
+ [3, 'Foobar', 2, "{$pre}numbers, {$pre}special_chars, {$pre}length"],
+ [3, '123456!', 2, "{$pre}letters, {$pre}length"],
+ [3, '7857375923752947', 2, "{$pre}letters, {$pre}special_chars"],
+ [3, 'FSDFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
+ [3, 'FÜKFJSLKFFSDFDSF', 2, "{$pre}lowercase_letters, {$pre}numbers, {$pre}special_chars"],
+ [3, 'fjsfjdljfsjsjjlsj', 2, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars"],
+
+ // Medium
+ [4, 'Foobar﴾', 3, "{$pre}numbers, {$pre}length"],
+ [4, 'foo-b0r!', 3, "{$pre}uppercase_letters, {$pre}length"],
+ [4, 'fjsfjdljfsjsjjls1', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ [4, '785737592375294b', 3, "{$pre}uppercase_letters, {$pre}special_chars"],
+ ];
+ }
+
/**
* @dataProvider provideStrong_passwords_will_passCases
*
@@ -269,12 +242,15 @@ public function strong_passwords_will_pass(string $value): void
$this->assertNoViolation();
}
- /** @test */
- public function constraint_get_default_option(): void
+ /**
+ * @return iterable
+ */
+ public static function provideStrong_passwords_will_passCases(): iterable
{
- $constraint = new PasswordStrength(5);
-
- self::assertEquals(5, $constraint->minStrength);
+ return [
+ ['Foobar$55_4&F'],
+ ['L33RoyJ3Jenkins!'],
+ ];
}
/** @test */
@@ -283,7 +259,7 @@ public function parameters_are_translated_when_translator_is_missing(): void
$this->validator = new PasswordStrengthValidator();
$this->validator->initialize($this->context);
- $constraint = new PasswordStrength(['minStrength' => 5, 'minLength' => 6]);
+ $constraint = new PasswordStrength(minStrength: 5, minLength: 6);
$this->validator->validate('FD43f.!', $constraint);