diff --git a/bundle/Spec/CirclicalUser/Factory/Listener/AccessListenerFactorySpec.php b/bundle/Spec/CirclicalUser/Factory/Listener/AccessListenerFactorySpec.php index 9aa837f..5b0f9d5 100644 --- a/bundle/Spec/CirclicalUser/Factory/Listener/AccessListenerFactorySpec.php +++ b/bundle/Spec/CirclicalUser/Factory/Listener/AccessListenerFactorySpec.php @@ -23,7 +23,6 @@ function it_supports_factory_interface(ServiceManager $serviceLocator, AccessSer $this->__invoke($serviceLocator, AccessListener::class)->shouldBeAnInstanceOf(AccessListener::class); } - function it_supports_factory_interface_with_strategy(ServiceManager $serviceLocator, AccessService $accessService, RedirectStrategy $redirectStrategy) { $config = [ @@ -46,7 +45,6 @@ function it_supports_factory_interface_with_strategy(ServiceManager $serviceLoca $this->__invoke($serviceLocator, AccessListener::class)->shouldBeAnInstanceOf(AccessListener::class); } - function it_throws_exceptions_for_absent_strategy_specifications(ServiceManager $serviceLocator, AccessService $accessService, RedirectStrategy $redirectStrategy) { $config = [ diff --git a/bundle/Spec/CirclicalUser/Factory/Service/AccessServiceFactorySpec.php b/bundle/Spec/CirclicalUser/Factory/Service/AccessServiceFactorySpec.php index d9d0381..2ee2030 100644 --- a/bundle/Spec/CirclicalUser/Factory/Service/AccessServiceFactorySpec.php +++ b/bundle/Spec/CirclicalUser/Factory/Service/AccessServiceFactorySpec.php @@ -11,16 +11,23 @@ use CirclicalUser\Service\AuthenticationService; use PhpSpec\ObjectBehavior; use Zend\ServiceManager\ServiceManager; +use CirclicalUser\Factory\Service\AccessServiceFactory; class AccessServiceFactorySpec extends ObjectBehavior { function it_is_initializable() { - $this->shouldHaveType('CirclicalUser\Factory\Service\AccessServiceFactory'); + $this->shouldHaveType(AccessServiceFactory::class); } - function it_creates_its_service(ServiceManager $serviceManager, RoleMapper $roleMapper, GroupPermissionProviderInterface $ruleMapper, UserPermissionProviderInterface $userActionRuleMapper, AuthenticationService $authenticationService, UserMapper $userMapper) - { + function it_creates_its_service( + ServiceManager $serviceManager, + RoleMapper $roleMapper, + GroupPermissionProviderInterface $ruleMapper, + UserPermissionProviderInterface $userActionRuleMapper, + AuthenticationService $authenticationService, + UserMapper $userMapper + ) { $config = [ 'circlical' => [ @@ -69,9 +76,15 @@ function it_creates_its_service(ServiceManager $serviceManager, RoleMapper $role $this->__invoke($serviceManager, AccessService::class)->shouldBeAnInstanceOf(AccessService::class); } - function it_creates_its_service_with_user_identity(ServiceManager $serviceManager, RoleMapper $roleMapper, GroupPermissionProviderInterface $ruleMapper, - UserPermissionProviderInterface $userActionRuleMapper, AuthenticationService $authenticationService, User $user, UserMapper $userMapper) - { + function it_creates_its_service_with_user_identity( + ServiceManager $serviceManager, + RoleMapper $roleMapper, + GroupPermissionProviderInterface $ruleMapper, + UserPermissionProviderInterface $userActionRuleMapper, + AuthenticationService $authenticationService, + User $user, + UserMapper $userMapper + ) { $config = [ 'circlical' => [ @@ -121,9 +134,15 @@ function it_creates_its_service_with_user_identity(ServiceManager $serviceManage } - function it_should_not_panic_when_guards_are_not_defined(ServiceManager $serviceManager, RoleMapper $roleMapper, GroupPermissionProviderInterface $ruleMapper, - UserPermissionProviderInterface $userActionRuleMapper, AuthenticationService $authenticationService, User $user, UserMapper $userMapper) - { + function it_should_not_panic_when_guards_are_not_defined( + ServiceManager $serviceManager, + RoleMapper $roleMapper, + GroupPermissionProviderInterface $ruleMapper, + UserPermissionProviderInterface $userActionRuleMapper, + AuthenticationService $authenticationService, + User $user, + UserMapper $userMapper + ) { $config = [ 'circlical' => [ 'user' => [ diff --git a/bundle/Spec/CirclicalUser/Factory/Service/AuthenticationServiceFactorySpec.php b/bundle/Spec/CirclicalUser/Factory/Service/AuthenticationServiceFactorySpec.php index 31c944f..d4d8092 100644 --- a/bundle/Spec/CirclicalUser/Factory/Service/AuthenticationServiceFactorySpec.php +++ b/bundle/Spec/CirclicalUser/Factory/Service/AuthenticationServiceFactorySpec.php @@ -5,6 +5,7 @@ use CirclicalUser\Mapper\AuthenticationMapper; use CirclicalUser\Mapper\RoleMapper; use CirclicalUser\Mapper\UserMapper; +use CirclicalUser\Provider\PasswordCheckerInterface; use CirclicalUser\Service\AuthenticationService; use CirclicalUser\Service\PasswordChecker\Zxcvbn; use PhpSpec\ObjectBehavior; @@ -12,6 +13,11 @@ class AuthenticationServiceFactorySpec extends ObjectBehavior { + public function let(ServiceManager $serviceManager, PasswordCheckerInterface $interface) + { + $serviceManager->get(PasswordCheckerInterface::class)->willReturn($interface); + } + public function it_is_initializable() { $this->shouldHaveType('CirclicalUser\Factory\Service\AuthenticationServiceFactory'); @@ -110,8 +116,5 @@ public function it_supports_password_checker_array_configs(ServiceManager $servi $service = $this->__invoke($serviceManager, AuthenticationService::class); $service->shouldBeAnInstanceOf(AuthenticationService::class); - $service->getPasswordChecker()->shouldBeAnInstanceOf(Zxcvbn::class); - $parameters = $service->getPasswordCheckerParameters(); - $parameters->shouldHaveKeyWithValue('required_strength', 3); } } diff --git a/bundle/Spec/CirclicalUser/Factory/Service/PasswordChecker/PasswordCheckerFactorySpec.php b/bundle/Spec/CirclicalUser/Factory/Service/PasswordChecker/PasswordCheckerFactorySpec.php new file mode 100644 index 0000000..14c3e8a --- /dev/null +++ b/bundle/Spec/CirclicalUser/Factory/Service/PasswordChecker/PasswordCheckerFactorySpec.php @@ -0,0 +1,77 @@ +shouldHaveType(PasswordCheckerFactory::class); + } + + public function it_creates_plain_types(ContainerInterface $container) + { + $config = [ + 'circlical' => [ + 'user' => [ + 'providers' => [ + 'role' => RoleMapper::class, + ], + ], + ], + ]; + $container->get('config')->willReturn($config); + $this->__invoke($container, PasswordCheckerInterface::class, [])->shouldBeAnInstanceOf(PasswordNotChecked::class); + } + + public function it_creates_specific_types(ContainerInterface $container) + { + $config = [ + 'circlical' => [ + 'user' => [ + 'providers' => [ + 'role' => RoleMapper::class, + ], + 'password_strength_checker' => [ + 'implementation' => \CirclicalUser\Service\PasswordChecker\Zxcvbn::class, + 'config' => ['required_strength' => 3,], + ], + ], + ], + ]; + $container->get('config')->willReturn($config); + $this->__invoke($container, PasswordCheckerInterface::class, [])->shouldBeAnInstanceOf(Zxcvbn::class); + } + + public function it_requires_options_when_array_notation_is_used(ContainerInterface $container) + { + $config = [ + 'circlical' => [ + 'user' => [ + 'providers' => [ + 'role' => RoleMapper::class, + ], + 'password_strength_checker' => [ + 'implementation' => \CirclicalUser\Service\PasswordChecker\Zxcvbn::class, + ], + ], + ], + ]; + $container->get('config')->willReturn($config); + $this->shouldThrow(PasswordStrengthCheckerException::class) + ->during('__invoke', [ + $container, + PasswordCheckerInterface::class, + [], + ]); + } +} diff --git a/bundle/Spec/CirclicalUser/Service/AuthenticationServiceSpec.php b/bundle/Spec/CirclicalUser/Service/AuthenticationServiceSpec.php index bf1fc6a..722ca40 100644 --- a/bundle/Spec/CirclicalUser/Service/AuthenticationServiceSpec.php +++ b/bundle/Spec/CirclicalUser/Service/AuthenticationServiceSpec.php @@ -73,7 +73,6 @@ public function let(AuthenticationMapper $authenticationMapper, UserMapper $user false, false, new PasswordNotChecked(), - [], // these are password checker options, typically defined in config true, true ); @@ -455,7 +454,6 @@ public function it_will_create_new_auth_records_with_strong_passwords($authentic false, false, new Passwdqc(), - [], true, true ); @@ -480,7 +478,6 @@ public function it_wont_create_new_auth_records_with_weak_passwords($authenticat false, false, new Passwdqc(), - [], true, true ); @@ -506,8 +503,7 @@ public function it_wont_create_new_auth_records_with_weak_passwords_via_zxcvbn( $this->systemEncryptionKey->getRawKeyMaterial(), false, false, - new Zxcvbn(), - [], + new Zxcvbn([]), true, true ); @@ -566,7 +562,6 @@ public function it_fails_to_create_tokens_when_password_changes_are_prohibited($ false, false, new PasswordNotChecked(), - [], true, true ); @@ -583,7 +578,6 @@ public function it_bails_on_password_changes_if_no_provider_is_set($authenticati false, false, new PasswordNotChecked(), - [], true, true ); diff --git a/composer.json b/composer.json index 7035266..de28920 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ "doctrine/orm": "^2.5||2.6.*", "paragonie/halite": "^3.3", "ramsey/uuid": "^3.5 | ^4", - "ramsey/uuid-doctrine": ">=1.5.0" + "ramsey/uuid-doctrine": ">=1.5.0", + "zendframework/zend-validator": "2.*" }, "require-dev": { "phpspec/phpspec": "6.1.1", diff --git a/config/module.config.php b/config/module.config.php index 077f37d..2adaaa0 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -9,7 +9,9 @@ use CirclicalUser\Factory\Listener\UserEntityListenerFactory; use CirclicalUser\Factory\Mapper\UserMapperFactory; use CirclicalUser\Factory\Service\AccessServiceFactory; +use CirclicalUser\Factory\Service\PasswordChecker\PasswordCheckerFactory; use CirclicalUser\Factory\Strategy\RedirectStrategyFactory; +use CirclicalUser\Factory\Validator\PasswordValidatorFactory; use CirclicalUser\Factory\View\Helper\ControllerAccessViewHelperFactory; use CirclicalUser\Factory\View\Helper\RoleAccessViewHelperFactory; use CirclicalUser\Listener\AccessListener; @@ -21,10 +23,12 @@ use CirclicalUser\Mapper\UserMapper; use CirclicalUser\Mapper\UserPermissionMapper; use CirclicalUser\Mapper\UserResetTokenMapper; +use CirclicalUser\Provider\PasswordCheckerInterface; use CirclicalUser\Service\AccessService; use CirclicalUser\Service\AuthenticationService; use CirclicalUser\Factory\Service\AuthenticationServiceFactory; use CirclicalUser\Strategy\RedirectStrategy; +use CirclicalUser\Validator\PasswordValidator; use CirclicalUser\View\Helper\ControllerAccessViewHelper; use CirclicalUser\View\Helper\RoleAccessViewHelper; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; @@ -91,6 +95,7 @@ UserEntityListener::class => UserEntityListenerFactory::class, UserMapper::class => UserMapperFactory::class, RedirectStrategy::class => RedirectStrategyFactory::class, + PasswordCheckerInterface::class => PasswordCheckerFactory::class, ], 'abstract_factories' => [ @@ -98,6 +103,12 @@ ], ], + 'validators' => [ + 'factories' => [ + PasswordValidator::class => PasswordValidatorFactory::class, + ], + ], + 'view_helpers' => [ 'aliases' => [ 'canAccessController' => ControllerAccessViewHelper::class, diff --git a/phpstan.neon b/phpstan.neon index c348217..ceac04e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,10 +3,4 @@ parameters: - %rootDir%/../../../vendor/autoload.php # excludes_analyse: level: 5 - ignoreErrors: -# - '#Call to an undefined method [A-Za-z0-9\\_]+::json().#' -# - '#Call to an undefined method [A-Za-z0-9\\_]+::auth().#' -# - '#Call to an undefined method [A-Za-z0-9\\_]+::locale().#' -# - '#Call to an undefined method [A-Za-z0-9\\_]+::flashMessenger().#' -# - '#Constructor of class Lemonade\\Service\\EmailListProvider\\CourseListProvider has an unused parameter \$userMapper#' -# - '#Constructor of class Lemonade\\Service\\EmailListProvider\\CourseStepListProvider has an unused parameter \$userMapper.#' \ No newline at end of file + ignoreErrors: \ No newline at end of file diff --git a/src/CirclicalUser/Factory/Service/AuthenticationServiceFactory.php b/src/CirclicalUser/Factory/Service/AuthenticationServiceFactory.php index a31bb01..206867b 100644 --- a/src/CirclicalUser/Factory/Service/AuthenticationServiceFactory.php +++ b/src/CirclicalUser/Factory/Service/AuthenticationServiceFactory.php @@ -2,10 +2,8 @@ namespace CirclicalUser\Factory\Service; -use CirclicalUser\Exception\PasswordStrengthCheckerException; use CirclicalUser\Mapper\UserResetTokenMapper; use CirclicalUser\Provider\PasswordCheckerInterface; -use CirclicalUser\Service\PasswordChecker\PasswordNotChecked; use Interop\Container\ContainerInterface; use Zend\ServiceManager\Factory\FactoryInterface; use CirclicalUser\Service\AuthenticationService; @@ -35,25 +33,6 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o $resetTokenProvider = $userConfig['providers']['reset'] ?? UserResetTokenMapper::class; } - $passwordChecker = null; - $passwordCheckerParameters = []; - if (!empty($userConfig['password_strength_checker'])) { - if (is_array($userConfig['password_strength_checker'])) { - if (!is_string($userConfig['password_strength_checker']['implementation'] ?? null) || !is_array($userConfig['password_strength_checker']['config'] ?? null)) { - throw new PasswordStrengthCheckerException("When using array notation, the password strength checker must contain 'implementation' and 'config'"); - } - $checkerImplementation = new $userConfig['password_strength_checker']['implementation']; - $passwordCheckerParameters = $userConfig['password_strength_checker']['config']; - } else { - $checkerImplementation = new $userConfig['password_strength_checker']; - } - - if ($checkerImplementation instanceof PasswordCheckerInterface) { - $passwordChecker = $checkerImplementation; - } - - } - return new AuthenticationService( $container->get($authMapper), $container->get($userProvider), @@ -61,8 +40,7 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o base64_decode($userConfig['auth']['crypto_key']), $userConfig['auth']['transient'], false, - $passwordChecker ?? new PasswordNotChecked(), - $passwordCheckerParameters, + $container->get(PasswordCheckerInterface::class), $userConfig['password_reset_tokens']['validate_fingerprint'] ?? true, $userConfig['password_reset_tokens']['validate_ip'] ?? false ); diff --git a/src/CirclicalUser/Factory/Service/PasswordChecker/PasswordCheckerFactory.php b/src/CirclicalUser/Factory/Service/PasswordChecker/PasswordCheckerFactory.php new file mode 100644 index 0000000..de5c632 --- /dev/null +++ b/src/CirclicalUser/Factory/Service/PasswordChecker/PasswordCheckerFactory.php @@ -0,0 +1,39 @@ +get('config'); + $userConfig = $config['circlical']['user']; + $passwordChecker = null; + if (!empty($userConfig['password_strength_checker'])) { + if (is_array($userConfig['password_strength_checker'])) { + if (!is_string($userConfig['password_strength_checker']['implementation'] ?? null) || !is_array($userConfig['password_strength_checker']['config'] ?? null)) { + throw new PasswordStrengthCheckerException("When using array notation, the password strength checker must contain 'implementation' and 'config'"); + } + $checkerImplementation = new $userConfig['password_strength_checker']['implementation']($userConfig['password_strength_checker']['config']); + } else { + $checkerImplementation = new $userConfig['password_strength_checker']; + } + + if (!$checkerImplementation instanceof PasswordCheckerInterface) { + throw new \RuntimeException("An invalid type of password checker was specified!"); + } + + return $checkerImplementation; + } + + return new PasswordNotChecked(); + } +} + diff --git a/src/CirclicalUser/Factory/Validator/PasswordValidatorFactory.php b/src/CirclicalUser/Factory/Validator/PasswordValidatorFactory.php new file mode 100644 index 0000000..f8a98ca --- /dev/null +++ b/src/CirclicalUser/Factory/Validator/PasswordValidatorFactory.php @@ -0,0 +1,18 @@ +get(PasswordCheckerInterface::class), $options); + } +} + diff --git a/src/CirclicalUser/Provider/PasswordCheckerInterface.php b/src/CirclicalUser/Provider/PasswordCheckerInterface.php index a40f2ff..4c6f6d3 100644 --- a/src/CirclicalUser/Provider/PasswordCheckerInterface.php +++ b/src/CirclicalUser/Provider/PasswordCheckerInterface.php @@ -2,9 +2,7 @@ namespace CirclicalUser\Provider; -use CirclicalUser\Provider\UserInterface as User; - interface PasswordCheckerInterface { - public function isStrongPassword(string $clearPassword, ?User $user, array $options): bool; + public function isStrongPassword(string $clearPassword, array $userData): bool; } \ No newline at end of file diff --git a/src/CirclicalUser/Service/AuthenticationService.php b/src/CirclicalUser/Service/AuthenticationService.php index b4eaa22..9218dc6 100644 --- a/src/CirclicalUser/Service/AuthenticationService.php +++ b/src/CirclicalUser/Service/AuthenticationService.php @@ -116,27 +116,19 @@ class AuthenticationService */ private $validateIp; - /** - * @var array - * Password-checker configuration as provided by config files - */ - private $passwordCheckerParameters; - /** * AuthenticationService constructor. * * @param AuthenticationProviderInterface $authenticationProvider * @param UserProviderInterface $userProvider - * @param ?UserResetTokenProviderInterface $resetTokenProvider If not null, permit password reset - * @param string $systemEncryptionKey The raw material of a Halite-generated encryption key, stored in config. - * @param bool $transient True if cookies should expire at the end of the session (zero value, for expiry) - * @param bool $secure True if cookies should be marked as 'Secure', enforced as 'true' in production by this service's Factory - * @param PasswordCheckerInterface $passwordChecker Optional, a password checker implementation - * @param array $passwordCheckerParameters Defined by config, the base parameters that are pushed into the password check sequence. Is not comprehensive, since - * some password checkers require user information that is pushed at invocation time. - * @param bool $validateFingerprint If password reset is enabled, do we validate the browser fingerprint? - * @param bool $validateIp If password reset is enabled, do we validate the user IP address? + * @param ?UserResetTokenProviderInterface $resetTokenProvider If not null, permit password reset + * @param string $systemEncryptionKey The raw material of a Halite-generated encryption key, stored in config. + * @param bool $transient True if cookies should expire at the end of the session (zero value, for expiry) + * @param bool $secure True if cookies should be marked as 'Secure', enforced as 'true' in production by this service's Factory + * @param PasswordCheckerInterface $passwordChecker Optional, a password checker implementation + * @param bool $validateFingerprint If password reset is enabled, do we validate the browser fingerprint? + * @param bool $validateIp If password reset is enabled, do we validate the user IP address? */ public function __construct( AuthenticationProviderInterface $authenticationProvider, @@ -146,7 +138,6 @@ public function __construct( bool $transient, bool $secure, PasswordCheckerInterface $passwordChecker, - array $passwordCheckerParameters, bool $validateFingerprint, bool $validateIp ) { @@ -159,7 +150,6 @@ public function __construct( $this->resetTokenProvider = $resetTokenProvider; $this->validateFingerprint = $validateFingerprint; $this->validateIp = $validateIp; - $this->passwordCheckerParameters = $passwordCheckerParameters; } /** @@ -181,18 +171,11 @@ private function setIdentity(User $user) $this->identity = $user; } - public function getPasswordChecker(): PasswordCheckerInterface { return $this->passwordChecker; } - public function getPasswordCheckerParameters(): array - { - return $this->passwordCheckerParameters; - } - - /** * Passed in by a successful form submission, should set proper auth cookies if the identity verifies. * The login should work with both username, and email address. @@ -476,7 +459,8 @@ private function purgeHashCookies(string $skipCookie = null) */ private function enforcePasswordStrength(string $password, User $user) { - if (!$this->passwordChecker->isStrongPassword($password, $user, $this->passwordCheckerParameters)) { + $userData = array_values(array_filter(array_values((array)$user), 'is_string')); + if (!$this->passwordChecker->isStrongPassword($password, $userData)) { throw new WeakPasswordException(); } } diff --git a/src/CirclicalUser/Service/PasswordChecker/Passwdqc.php b/src/CirclicalUser/Service/PasswordChecker/Passwdqc.php index 1e30f90..c04c7e1 100644 --- a/src/CirclicalUser/Service/PasswordChecker/Passwdqc.php +++ b/src/CirclicalUser/Service/PasswordChecker/Passwdqc.php @@ -3,11 +3,17 @@ namespace CirclicalUser\Service\PasswordChecker; use CirclicalUser\Provider\PasswordCheckerInterface; -use CirclicalUser\Provider\UserInterface; class Passwdqc implements PasswordCheckerInterface { - public function isStrongPassword(string $clearPassword, ?UserInterface $user, array $options = []): bool + private $creationOptions; + + public function __construct(array $creationOptions = null) + { + $this->creationOptions = $creationOptions; + } + + public function isStrongPassword(string $clearPassword, array $userData): bool { return (new \ParagonIE\Passwdqc\Passwdqc())->check($clearPassword); } diff --git a/src/CirclicalUser/Service/PasswordChecker/PasswordNotChecked.php b/src/CirclicalUser/Service/PasswordChecker/PasswordNotChecked.php index 6e445d5..a1dec7d 100644 --- a/src/CirclicalUser/Service/PasswordChecker/PasswordNotChecked.php +++ b/src/CirclicalUser/Service/PasswordChecker/PasswordNotChecked.php @@ -3,11 +3,17 @@ namespace CirclicalUser\Service\PasswordChecker; use CirclicalUser\Provider\PasswordCheckerInterface; -use CirclicalUser\Provider\UserInterface; class PasswordNotChecked implements PasswordCheckerInterface { - public function isStrongPassword(string $clearPassword, ?UserInterface $user, array $options): bool + private $creationOptions; + + public function __construct(array $creationOptions = null) + { + $this->creationOptions = $creationOptions; + } + + public function isStrongPassword(string $clearPassword, array $userData): bool { return true; } diff --git a/src/CirclicalUser/Service/PasswordChecker/Zxcvbn.php b/src/CirclicalUser/Service/PasswordChecker/Zxcvbn.php index 7d37e2a..467e7c2 100644 --- a/src/CirclicalUser/Service/PasswordChecker/Zxcvbn.php +++ b/src/CirclicalUser/Service/PasswordChecker/Zxcvbn.php @@ -3,17 +3,22 @@ namespace CirclicalUser\Service\PasswordChecker; use CirclicalUser\Provider\PasswordCheckerInterface; -use CirclicalUser\Provider\UserInterface; class Zxcvbn implements PasswordCheckerInterface { + private $creationOptions; + + public function __construct(array $creationOptions) + { + $this->creationOptions = $creationOptions; + } + /** * Check strength using the excellent zxcvbn library. */ - public function isStrongPassword(string $clearPassword, ?UserInterface $user, array $options): bool + public function isStrongPassword(string $clearPassword, array $userData): bool { - $requiredStrength = $options['required_strength'] ?? 4; - $userData = array_values(array_filter($user ? array_values((array)$user) : [], 'is_string')); + $requiredStrength = $this->creationOptions['required_strength'] ?? 4; $strength = (new \ZxcvbnPhp\Zxcvbn())->passwordStrength($clearPassword, $userData); return $strength['score'] >= $requiredStrength; diff --git a/src/CirclicalUser/Validator/PasswordValidator.php b/src/CirclicalUser/Validator/PasswordValidator.php new file mode 100644 index 0000000..76a017d --- /dev/null +++ b/src/CirclicalUser/Validator/PasswordValidator.php @@ -0,0 +1,46 @@ + 'That password is common, or could be easily guessed or generated. Please create a stronger password.', + ]; + + private $passwordChecker; + private $options; + + public function __construct(PasswordCheckerInterface $passwordChecker, array $options = null) + { + $this->passwordChecker = $passwordChecker; + $this->options = $options; + + parent::__construct($options); + } + + public function isValid($value, $context = null): bool + { + $userData = []; + if (is_array($context) && is_array($this->options) && isset($this->options['user_data']) && is_array($this->options['user_data'])) { + foreach ($this->options['user_data'] as $key) { + if (is_string($context[$key])) { + $userData[] = $context[$key]; + } + } + } + + if (!$this->passwordChecker->isStrongPassword($value, $userData)) { + $this->error(self::WEAK_PASSWORD); + + return false; + } + + return true; + } +} \ No newline at end of file