diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 65c1c0b..b71b58a 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -22,8 +22,4 @@
./src
-
-
-
-
diff --git a/src/DependencyInjection/RollerworksPasswordStrengthExtension.php b/src/DependencyInjection/RollerworksPasswordStrengthExtension.php
index 8a5c9f9..598867e 100644
--- a/src/DependencyInjection/RollerworksPasswordStrengthExtension.php
+++ b/src/DependencyInjection/RollerworksPasswordStrengthExtension.php
@@ -36,6 +36,7 @@ public function load(array $configs, ContainerBuilder $container)
$container->setAlias('rollerworks_password_strength.blacklist_provider', $config['blacklist']['default_provider']);
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader->load('strength_validator.xml');
$loader->load('blacklist.xml');
if (isset($config['blacklist']['providers']['sqlite'])) {
diff --git a/src/Resources/config/strength_validator.xml b/src/Resources/config/strength_validator.xml
new file mode 100644
index 0000000..0371e77
--- /dev/null
+++ b/src/Resources/config/strength_validator.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Resources/translations/validators.en.xlf b/src/Resources/translations/validators.en.xlf
new file mode 100644
index 0000000..d78884b
--- /dev/null
+++ b/src/Resources/translations/validators.en.xlf
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+ Password must be at least {{length}} characters long.
+
+
+
+ Password must include at least one letter.
+
+
+
+ Password must include both upper and lower case letters.
+
+
+
+ Password must include at least one number.
+
+
+
+ Password must contain at least one special character.
+
+
+
+ Password needs to be at least at strength level "{{ min_strength }}", current level is "{{ current_strength }}", try the following {{ strength_tips }}.
+
+
+
+
+
+ Very Weak
+
+
+
+ Weak
+
+
+
+ Medium
+
+
+
+ Strong
+
+
+
+ Very strong
+
+
+
+
+
+ add (upper/lowercase) letters
+
+
+
+ add numbers
+
+
+
+ add uppercase letters
+
+
+
+ add uppercase letters
+
+
+
+ add special characters
+
+
+
+ add more characters
+
+
+
+
diff --git a/src/Resources/translations/validators.nl.xlf b/src/Resources/translations/validators.nl.xlf
new file mode 100644
index 0000000..4bc47f1
--- /dev/null
+++ b/src/Resources/translations/validators.nl.xlf
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+ Wachtwoord moet minstens {{length}} tekens lang zijn.
+
+
+
+ Wachtwoord moet ten minste één letter bevatten.
+
+
+
+ Wachtwoord moet ten minste één hoofdletter en kleine letter bevatten.
+
+
+
+ Wachtwoord moet ten minste één nummer bevatten.
+
+
+
+ Wachtwoord moet ten minste één speciaal teken of leesteken bevatten.
+
+
+
+ Wachtwoord moet minstens aan sterkte niveau "{{ min_strength }}" voldoen, huidig niveau is “{{ current_strength }}” probeer het volgende: {{ strength_tips }}.
+
+
+
+
+
+ Erg zwak
+
+
+
+ Zwak
+
+
+
+ Gemiddeld
+
+
+
+ Sterk
+
+
+
+ Zeer sterk
+
+
+
+
+
+ voeg (hoofd/kleine) letters toe
+
+
+
+ voeg nummers toe
+
+
+
+ voeg hoofdletters toe
+
+
+
+ voeg kleine letters toe
+
+
+
+ voeg speciaal tekens of leestekens toe
+
+
+
+ gebruik meer tekens
+
+
+
+
diff --git a/src/Validator/Constraints/PasswordStrength.php b/src/Validator/Constraints/PasswordStrength.php
index dd5bcb1..3e0d42c 100644
--- a/src/Validator/Constraints/PasswordStrength.php
+++ b/src/Validator/Constraints/PasswordStrength.php
@@ -34,4 +34,9 @@ public function getRequiredOptions()
{
return array('minStrength');
}
+
+ public function validatedBy()
+ {
+ return 'rollerworks_password_strength';
+ }
}
diff --git a/src/Validator/Constraints/PasswordStrengthValidator.php b/src/Validator/Constraints/PasswordStrengthValidator.php
index e414970..4251e8c 100644
--- a/src/Validator/Constraints/PasswordStrengthValidator.php
+++ b/src/Validator/Constraints/PasswordStrengthValidator.php
@@ -11,6 +11,9 @@
namespace Rollerworks\Bundle\PasswordStrengthBundle\Validator\Constraints;
+use Symfony\Component\Translation\Loader\XliffFileLoader;
+use Symfony\Component\Translation\Translator;
+use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
@@ -38,6 +41,35 @@
*/
class PasswordStrengthValidator extends ConstraintValidator
{
+ /**
+ * @var TranslatorInterface
+ */
+ private $translator;
+
+ /**
+ * @var array
+ */
+ private static $levelToLabel = array(
+ 1 => 'very_weak',
+ 2 => 'weak',
+ 3 => 'medium',
+ 4 => 'strong',
+ 5 => 'very_strong',
+ );
+
+ public function __construct(TranslatorInterface $translator = null)
+ {
+ // If translator is missing create a new translator.
+ // With the 'en' locale and 'validators' domain.
+ if (null === $translator) {
+ $translator = new Translator('en');
+ $translator->addLoader('xlf', new XliffFileLoader());
+ $translator->addResource('xlf', dirname(dirname(__DIR__)).'/Resources/translations/validators.en.xlf', 'en', 'validators');
+ }
+
+ $this->translator = $translator;
+ }
+
/**
* @param string $password
* @param PasswordStrength|Constraint $constraint
@@ -69,35 +101,65 @@ public function validate($password, Constraint $constraint)
return;
}
+ $tips = array();
+
if (preg_match('/[a-zA-Z]/', $password)) {
++$passwordStrength;
- if (preg_match('/[a-z]/', $password) && preg_match('/[A-Z]/', $password)) {
+
+ 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';
}
if ($passLength > 12) {
++$passwordStrength;
+ } else {
+ $tips[] = 'length';
}
// No decrease strength on weak combinations
if ($passwordStrength < $constraint->minStrength) {
+ $parameters = array(
+ '{{ length }}' => $constraint->minLength,
+ '{{ min_strength }}' => $this->translator->trans('rollerworks_password.strength_level.'.self::$levelToLabel[$constraint->minStrength], array(), 'validators'),
+ '{{ current_strength }}' => $this->translator->trans('rollerworks_password.strength_level.'.self::$levelToLabel[$passwordStrength], array(), 'validators'),
+ '{{ strength_tips }}' => implode(', ', array_map(array($this, 'translateTips'), $tips)),
+ );
+
if ($this->context instanceof ExecutionContextInterface) {
$this->context->buildViolation($constraint->message)
- ->setParameters(array('{{ length }}' => $constraint->minLength))
+ ->setParameters($parameters)
->addViolation();
} else {
- $this->context->addViolation($constraint->message, array('{{ length }}' => $constraint->minLength));
+ $this->context->addViolation($constraint->message, $parameters);
}
}
}
+
+ /**
+ * @internal
+ */
+ public function translateTips($tip)
+ {
+ return $this->translator->trans('rollerworks_password.tip.'.$tip, array(), 'validators');
+ }
}
diff --git a/tests/DependencyInjection/ExtensionTest.php b/tests/DependencyInjection/ExtensionTest.php
index 6724d8a..64349af 100644
--- a/tests/DependencyInjection/ExtensionTest.php
+++ b/tests/DependencyInjection/ExtensionTest.php
@@ -13,8 +13,10 @@
use Rollerworks\Bundle\PasswordStrengthBundle\DependencyInjection\RollerworksPasswordStrengthExtension;
use Rollerworks\Bundle\PasswordStrengthBundle\Validator\Constraints\Blacklist as BlacklistConstraint;
+use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
+use Symfony\Component\Validator\Tests\Fixtures\Reference;
class ExtensionTest extends \PHPUnit_Framework_TestCase
{
@@ -107,6 +109,30 @@ public function testLoadWithChainConfiguration()
$this->assertFalse($provider->isBlacklisted('leeRoy'));
}
+ public function testPasswordStrengthValidatorService()
+ {
+ $container = $this->createContainer();
+ $container->registerExtension(new RollerworksPasswordStrengthExtension());
+ $container->loadFromExtension('rollerworks_password_strength');
+
+ $container->addCompilerPass(new AddConstraintValidatorsPass());
+ $container->register(
+ 'validator.validator_factory',
+ 'Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory'
+ )->setArguments(array(new Reference('service_container'), array()));
+
+ $this->compileContainer($container);
+
+ $validatorFactory = $container->getDefinition('validator.validator_factory');
+ $factoryArguments = $validatorFactory->getArguments();
+
+ $this->assertArrayHasKey('rollerworks_password_strength', $factoryArguments[1]);
+ $this->assertEquals(
+ 'rollerworks_password_strength.validator.password_strength',
+ $factoryArguments[1]['rollerworks_password_strength']
+ );
+ }
+
/**
* @return ContainerBuilder
*/
diff --git a/tests/Validator/PasswordStrengthTest.php b/tests/Validator/PasswordStrengthTest.php
index 283486b..92284ee 100644
--- a/tests/Validator/PasswordStrengthTest.php
+++ b/tests/Validator/PasswordStrengthTest.php
@@ -13,11 +13,23 @@
use Rollerworks\Bundle\PasswordStrengthBundle\Validator\Constraints\PasswordStrength;
use Rollerworks\Bundle\PasswordStrengthBundle\Validator\Constraints\PasswordStrengthValidator;
+use Symfony\Component\Translation\Translator;
use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
use Symfony\Component\Validator\Validation;
class PasswordStrengthTest extends AbstractConstraintValidatorTest
{
+ /**
+ * @var array
+ */
+ private static $levelToLabel = array(
+ 1 => 'very_weak',
+ 2 => 'weak',
+ 3 => 'medium',
+ 4 => 'strong',
+ 5 => 'very_strong',
+ );
+
protected function getApiVersion()
{
return Validation::API_VERSION_2_5;
@@ -25,7 +37,7 @@ protected function getApiVersion()
protected function createValidator()
{
- return new PasswordStrengthValidator();
+ return new PasswordStrengthValidator(new Translator('en'));
}
public function testNullIsValid()
@@ -50,35 +62,31 @@ public function testExpectsStringCompatibleType()
$this->validator->validate(new \stdClass(), new PasswordStrength(5));
}
- public static function getVeryWeakPasswords()
+ public function getWeakPasswords()
{
- return array(
- array('weaker'),
- array('123456'),
- array('foobar'),
- array('!.!.!.'),
- );
- }
+ $pre = 'rollerworks_password.tip.';
- public static function getWeakPasswords()
- {
return array(
- array('wee6eak'),
- array('foobar!'),
- array('Foobar'),
- array('123456!'),
- array('7857375923752947'),
- array('fjsfjdljfsjsjjlsj'),
- );
- }
-
- public static function getMediumPasswords()
- {
- return array(
- array('Foobar!'),
- array('foo-b0r!'),
- array('fjsfjdljfsjsjjls1'),
- array('785737592375294b'),
+ // 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, 'foobar', 1, "{$pre}uppercase_letters, {$pre}numbers, {$pre}special_chars, {$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, '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"),
);
}
@@ -100,66 +108,31 @@ public static function getVeryStrongPasswords()
);
}
- /**
- * @dataProvider getVeryWeakPasswords
- */
- public function testVeryWeakPasswords($value)
- {
- $constraint = new PasswordStrength(2);
-
- $this->validator->validate($value, $constraint);
-
- $this->buildViolation('password_too_weak')
- ->setParameters(array('{{ length }}' => 6))
- ->assertRaised();
- }
-
/**
* @dataProvider getWeakPasswords
*/
- public function testWeakPasswords($value)
- {
- $constraint = new PasswordStrength(array('minStrength' => 3, 'minLength' => 7));
-
- $this->validator->validate($value, $constraint);
-
- $this->buildViolation('password_too_weak')
- ->setParameters(array('{{ length }}' => 7))
- ->assertRaised();
- }
-
- /**
- * @dataProvider getMediumPasswords
- */
- public function testMediumPasswords($value)
+ public function testWeakPasswordsWillNotPass($minStrength, $value, $currentStrength, $tips = '')
{
- $constraint = new PasswordStrength(4);
+ $constraint = new PasswordStrength(array('minStrength' => $minStrength, 'minLength' => 6));
$this->validator->validate($value, $constraint);
- $this->buildViolation('password_too_weak')
- ->setParameters(array('{{ length }}' => 6))
- ->assertRaised();
- }
-
- /**
- * @dataProvider getStrongPasswords
- */
- public function testStrongPasswords($value)
- {
- $constraint = new PasswordStrength(5);
-
- $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(array('{{ length }}' => 6))
+ ->setParameters($parameters)
->assertRaised();
}
/**
* @dataProvider getVeryStrongPasswords
*/
- public function testVeryStrongPasswords($value)
+ public function testStrongPasswordsWillPass($value)
{
$constraint = new PasswordStrength(5);
@@ -168,66 +141,31 @@ public function testVeryStrongPasswords($value)
$this->assertNoViolation();
}
- /**
- * @dataProvider getVeryWeakPasswords
- */
- public function testVeryWeakPasswordWillNotPass($value)
+ public function testConstraintGetDefaultOption()
{
- $constraint = new PasswordStrength(2);
-
- $this->validator->validate($value, $constraint);
+ $constraint = new PasswordStrength(5);
- $this->buildViolation('password_too_weak')
- ->setParameters(array('{{ length }}' => 6))
- ->assertRaised();
+ $this->assertEquals(5, $constraint->minStrength);
}
- /**
- * @dataProvider getWeakPasswords
- */
- public function testWeakPasswordsWillNotPass($value)
+ public function testParametersAreTranslatedWhenTranslatorIsMissing()
{
- $constraint = new PasswordStrength(3);
+ $this->validator = new PasswordStrengthValidator();
+ $this->validator->initialize($this->context);
- $this->validator->validate($value, $constraint);
-
- $this->buildViolation('password_too_weak')
- ->setParameters(array('{{ length }}' => 6))
- ->assertRaised();
- }
+ $constraint = new PasswordStrength(array('minStrength' => 5, 'minLength' => 6));
- /**
- * @dataProvider getMediumPasswords
- */
- public function testMediumPasswordWillNotPass($value)
- {
- $constraint = new PasswordStrength(4);
+ $this->validator->validate('FD43f.!', $constraint);
- $this->validator->validate($value, $constraint);
-
- $this->buildViolation('password_too_weak')
- ->setParameters(array('{{ length }}' => 6))
- ->assertRaised();
- }
-
- /**
- * @dataProvider getStrongPasswords
- */
- public function testStrongPasswordWillNotPass($value)
- {
- $constraint = new PasswordStrength(5);
-
- $this->validator->validate($value, $constraint);
+ $parameters = array(
+ '{{ length }}' => 6,
+ '{{ current_strength }}' => 'Strong',
+ '{{ min_strength }}' => 'Very strong',
+ '{{ strength_tips }}' => 'add more characters',
+ );
$this->buildViolation('password_too_weak')
- ->setParameters(array('{{ length }}' => 6))
+ ->setParameters($parameters)
->assertRaised();
}
-
- public function testConstraintGetDefaultOption()
- {
- $constraint = new PasswordStrength(5);
-
- $this->assertEquals(5, $constraint->minStrength);
- }
}