Skip to content

Commit a488508

Browse files
authored
Merge pull request #54 from Saeven/feature/password-validator
Feature/password validator
2 parents f95d2f9 + 08d2249 commit a488508

File tree

17 files changed

+265
-88
lines changed

17 files changed

+265
-88
lines changed

bundle/Spec/CirclicalUser/Factory/Listener/AccessListenerFactorySpec.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ function it_supports_factory_interface(ServiceManager $serviceLocator, AccessSer
2323
$this->__invoke($serviceLocator, AccessListener::class)->shouldBeAnInstanceOf(AccessListener::class);
2424
}
2525

26-
2726
function it_supports_factory_interface_with_strategy(ServiceManager $serviceLocator, AccessService $accessService, RedirectStrategy $redirectStrategy)
2827
{
2928
$config = [
@@ -46,7 +45,6 @@ function it_supports_factory_interface_with_strategy(ServiceManager $serviceLoca
4645
$this->__invoke($serviceLocator, AccessListener::class)->shouldBeAnInstanceOf(AccessListener::class);
4746
}
4847

49-
5048
function it_throws_exceptions_for_absent_strategy_specifications(ServiceManager $serviceLocator, AccessService $accessService, RedirectStrategy $redirectStrategy)
5149
{
5250
$config = [

bundle/Spec/CirclicalUser/Factory/Service/AccessServiceFactorySpec.php

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,23 @@
1111
use CirclicalUser\Service\AuthenticationService;
1212
use PhpSpec\ObjectBehavior;
1313
use Zend\ServiceManager\ServiceManager;
14+
use CirclicalUser\Factory\Service\AccessServiceFactory;
1415

1516
class AccessServiceFactorySpec extends ObjectBehavior
1617
{
1718
function it_is_initializable()
1819
{
19-
$this->shouldHaveType('CirclicalUser\Factory\Service\AccessServiceFactory');
20+
$this->shouldHaveType(AccessServiceFactory::class);
2021
}
2122

22-
function it_creates_its_service(ServiceManager $serviceManager, RoleMapper $roleMapper, GroupPermissionProviderInterface $ruleMapper, UserPermissionProviderInterface $userActionRuleMapper, AuthenticationService $authenticationService, UserMapper $userMapper)
23-
{
23+
function it_creates_its_service(
24+
ServiceManager $serviceManager,
25+
RoleMapper $roleMapper,
26+
GroupPermissionProviderInterface $ruleMapper,
27+
UserPermissionProviderInterface $userActionRuleMapper,
28+
AuthenticationService $authenticationService,
29+
UserMapper $userMapper
30+
) {
2431
$config = [
2532

2633
'circlical' => [
@@ -69,9 +76,15 @@ function it_creates_its_service(ServiceManager $serviceManager, RoleMapper $role
6976
$this->__invoke($serviceManager, AccessService::class)->shouldBeAnInstanceOf(AccessService::class);
7077
}
7178

72-
function it_creates_its_service_with_user_identity(ServiceManager $serviceManager, RoleMapper $roleMapper, GroupPermissionProviderInterface $ruleMapper,
73-
UserPermissionProviderInterface $userActionRuleMapper, AuthenticationService $authenticationService, User $user, UserMapper $userMapper)
74-
{
79+
function it_creates_its_service_with_user_identity(
80+
ServiceManager $serviceManager,
81+
RoleMapper $roleMapper,
82+
GroupPermissionProviderInterface $ruleMapper,
83+
UserPermissionProviderInterface $userActionRuleMapper,
84+
AuthenticationService $authenticationService,
85+
User $user,
86+
UserMapper $userMapper
87+
) {
7588
$config = [
7689

7790
'circlical' => [
@@ -121,9 +134,15 @@ function it_creates_its_service_with_user_identity(ServiceManager $serviceManage
121134
}
122135

123136

124-
function it_should_not_panic_when_guards_are_not_defined(ServiceManager $serviceManager, RoleMapper $roleMapper, GroupPermissionProviderInterface $ruleMapper,
125-
UserPermissionProviderInterface $userActionRuleMapper, AuthenticationService $authenticationService, User $user, UserMapper $userMapper)
126-
{
137+
function it_should_not_panic_when_guards_are_not_defined(
138+
ServiceManager $serviceManager,
139+
RoleMapper $roleMapper,
140+
GroupPermissionProviderInterface $ruleMapper,
141+
UserPermissionProviderInterface $userActionRuleMapper,
142+
AuthenticationService $authenticationService,
143+
User $user,
144+
UserMapper $userMapper
145+
) {
127146
$config = [
128147
'circlical' => [
129148
'user' => [

bundle/Spec/CirclicalUser/Factory/Service/AuthenticationServiceFactorySpec.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
use CirclicalUser\Mapper\AuthenticationMapper;
66
use CirclicalUser\Mapper\RoleMapper;
77
use CirclicalUser\Mapper\UserMapper;
8+
use CirclicalUser\Provider\PasswordCheckerInterface;
89
use CirclicalUser\Service\AuthenticationService;
910
use CirclicalUser\Service\PasswordChecker\Zxcvbn;
1011
use PhpSpec\ObjectBehavior;
1112
use Zend\ServiceManager\ServiceManager;
1213

1314
class AuthenticationServiceFactorySpec extends ObjectBehavior
1415
{
16+
public function let(ServiceManager $serviceManager, PasswordCheckerInterface $interface)
17+
{
18+
$serviceManager->get(PasswordCheckerInterface::class)->willReturn($interface);
19+
}
20+
1521
public function it_is_initializable()
1622
{
1723
$this->shouldHaveType('CirclicalUser\Factory\Service\AuthenticationServiceFactory');
@@ -110,8 +116,5 @@ public function it_supports_password_checker_array_configs(ServiceManager $servi
110116

111117
$service = $this->__invoke($serviceManager, AuthenticationService::class);
112118
$service->shouldBeAnInstanceOf(AuthenticationService::class);
113-
$service->getPasswordChecker()->shouldBeAnInstanceOf(Zxcvbn::class);
114-
$parameters = $service->getPasswordCheckerParameters();
115-
$parameters->shouldHaveKeyWithValue('required_strength', 3);
116119
}
117120
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace Spec\CirclicalUser\Factory\Service\PasswordChecker;
4+
5+
use CirclicalUser\Exception\PasswordStrengthCheckerException;
6+
use CirclicalUser\Factory\Service\PasswordChecker\PasswordCheckerFactory;
7+
use CirclicalUser\Mapper\RoleMapper;
8+
use CirclicalUser\Provider\PasswordCheckerInterface;
9+
use CirclicalUser\Service\PasswordChecker\PasswordNotChecked;
10+
use CirclicalUser\Service\PasswordChecker\Zxcvbn;
11+
use Interop\Container\ContainerInterface;
12+
use PhpSpec\ObjectBehavior;
13+
14+
class PasswordCheckerFactorySpec extends ObjectBehavior
15+
{
16+
function it_is_initializable()
17+
{
18+
$this->shouldHaveType(PasswordCheckerFactory::class);
19+
}
20+
21+
public function it_creates_plain_types(ContainerInterface $container)
22+
{
23+
$config = [
24+
'circlical' => [
25+
'user' => [
26+
'providers' => [
27+
'role' => RoleMapper::class,
28+
],
29+
],
30+
],
31+
];
32+
$container->get('config')->willReturn($config);
33+
$this->__invoke($container, PasswordCheckerInterface::class, [])->shouldBeAnInstanceOf(PasswordNotChecked::class);
34+
}
35+
36+
public function it_creates_specific_types(ContainerInterface $container)
37+
{
38+
$config = [
39+
'circlical' => [
40+
'user' => [
41+
'providers' => [
42+
'role' => RoleMapper::class,
43+
],
44+
'password_strength_checker' => [
45+
'implementation' => \CirclicalUser\Service\PasswordChecker\Zxcvbn::class,
46+
'config' => ['required_strength' => 3,],
47+
],
48+
],
49+
],
50+
];
51+
$container->get('config')->willReturn($config);
52+
$this->__invoke($container, PasswordCheckerInterface::class, [])->shouldBeAnInstanceOf(Zxcvbn::class);
53+
}
54+
55+
public function it_requires_options_when_array_notation_is_used(ContainerInterface $container)
56+
{
57+
$config = [
58+
'circlical' => [
59+
'user' => [
60+
'providers' => [
61+
'role' => RoleMapper::class,
62+
],
63+
'password_strength_checker' => [
64+
'implementation' => \CirclicalUser\Service\PasswordChecker\Zxcvbn::class,
65+
],
66+
],
67+
],
68+
];
69+
$container->get('config')->willReturn($config);
70+
$this->shouldThrow(PasswordStrengthCheckerException::class)
71+
->during('__invoke', [
72+
$container,
73+
PasswordCheckerInterface::class,
74+
[],
75+
]);
76+
}
77+
}

bundle/Spec/CirclicalUser/Service/AuthenticationServiceSpec.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ public function let(AuthenticationMapper $authenticationMapper, UserMapper $user
7373
false,
7474
false,
7575
new PasswordNotChecked(),
76-
[], // these are password checker options, typically defined in config
7776
true,
7877
true
7978
);
@@ -455,7 +454,6 @@ public function it_will_create_new_auth_records_with_strong_passwords($authentic
455454
false,
456455
false,
457456
new Passwdqc(),
458-
[],
459457
true,
460458
true
461459
);
@@ -480,7 +478,6 @@ public function it_wont_create_new_auth_records_with_weak_passwords($authenticat
480478
false,
481479
false,
482480
new Passwdqc(),
483-
[],
484481
true,
485482
true
486483
);
@@ -506,8 +503,7 @@ public function it_wont_create_new_auth_records_with_weak_passwords_via_zxcvbn(
506503
$this->systemEncryptionKey->getRawKeyMaterial(),
507504
false,
508505
false,
509-
new Zxcvbn(),
510-
[],
506+
new Zxcvbn([]),
511507
true,
512508
true
513509
);
@@ -566,7 +562,6 @@ public function it_fails_to_create_tokens_when_password_changes_are_prohibited($
566562
false,
567563
false,
568564
new PasswordNotChecked(),
569-
[],
570565
true,
571566
true
572567
);
@@ -583,7 +578,6 @@ public function it_bails_on_password_changes_if_no_provider_is_set($authenticati
583578
false,
584579
false,
585580
new PasswordNotChecked(),
586-
[],
587581
true,
588582
true
589583
);

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"doctrine/orm": "^2.5||2.6.*",
3737
"paragonie/halite": "^3.3",
3838
"ramsey/uuid": "^3.5 | ^4",
39-
"ramsey/uuid-doctrine": ">=1.5.0"
39+
"ramsey/uuid-doctrine": ">=1.5.0",
40+
"zendframework/zend-validator": "2.*"
4041
},
4142
"require-dev": {
4243
"phpspec/phpspec": "6.1.1",

config/module.config.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
use CirclicalUser\Factory\Listener\UserEntityListenerFactory;
1010
use CirclicalUser\Factory\Mapper\UserMapperFactory;
1111
use CirclicalUser\Factory\Service\AccessServiceFactory;
12+
use CirclicalUser\Factory\Service\PasswordChecker\PasswordCheckerFactory;
1213
use CirclicalUser\Factory\Strategy\RedirectStrategyFactory;
14+
use CirclicalUser\Factory\Validator\PasswordValidatorFactory;
1315
use CirclicalUser\Factory\View\Helper\ControllerAccessViewHelperFactory;
1416
use CirclicalUser\Factory\View\Helper\RoleAccessViewHelperFactory;
1517
use CirclicalUser\Listener\AccessListener;
@@ -21,10 +23,12 @@
2123
use CirclicalUser\Mapper\UserMapper;
2224
use CirclicalUser\Mapper\UserPermissionMapper;
2325
use CirclicalUser\Mapper\UserResetTokenMapper;
26+
use CirclicalUser\Provider\PasswordCheckerInterface;
2427
use CirclicalUser\Service\AccessService;
2528
use CirclicalUser\Service\AuthenticationService;
2629
use CirclicalUser\Factory\Service\AuthenticationServiceFactory;
2730
use CirclicalUser\Strategy\RedirectStrategy;
31+
use CirclicalUser\Validator\PasswordValidator;
2832
use CirclicalUser\View\Helper\ControllerAccessViewHelper;
2933
use CirclicalUser\View\Helper\RoleAccessViewHelper;
3034
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
@@ -91,13 +95,20 @@
9195
UserEntityListener::class => UserEntityListenerFactory::class,
9296
UserMapper::class => UserMapperFactory::class,
9397
RedirectStrategy::class => RedirectStrategyFactory::class,
98+
PasswordCheckerInterface::class => PasswordCheckerFactory::class,
9499
],
95100

96101
'abstract_factories' => [
97102
AbstractDoctrineMapperFactory::class,
98103
],
99104
],
100105

106+
'validators' => [
107+
'factories' => [
108+
PasswordValidator::class => PasswordValidatorFactory::class,
109+
],
110+
],
111+
101112
'view_helpers' => [
102113
'aliases' => [
103114
'canAccessController' => ControllerAccessViewHelper::class,

phpstan.neon

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,4 @@ parameters:
33
- %rootDir%/../../../vendor/autoload.php
44
# excludes_analyse:
55
level: 5
6-
ignoreErrors:
7-
# - '#Call to an undefined method [A-Za-z0-9\\_]+::json().#'
8-
# - '#Call to an undefined method [A-Za-z0-9\\_]+::auth().#'
9-
# - '#Call to an undefined method [A-Za-z0-9\\_]+::locale().#'
10-
# - '#Call to an undefined method [A-Za-z0-9\\_]+::flashMessenger().#'
11-
# - '#Constructor of class Lemonade\\Service\\EmailListProvider\\CourseListProvider has an unused parameter \$userMapper#'
12-
# - '#Constructor of class Lemonade\\Service\\EmailListProvider\\CourseStepListProvider has an unused parameter \$userMapper.#'
6+
ignoreErrors:

src/CirclicalUser/Factory/Service/AuthenticationServiceFactory.php

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33
namespace CirclicalUser\Factory\Service;
44

5-
use CirclicalUser\Exception\PasswordStrengthCheckerException;
65
use CirclicalUser\Mapper\UserResetTokenMapper;
76
use CirclicalUser\Provider\PasswordCheckerInterface;
8-
use CirclicalUser\Service\PasswordChecker\PasswordNotChecked;
97
use Interop\Container\ContainerInterface;
108
use Zend\ServiceManager\Factory\FactoryInterface;
119
use CirclicalUser\Service\AuthenticationService;
@@ -35,34 +33,14 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o
3533
$resetTokenProvider = $userConfig['providers']['reset'] ?? UserResetTokenMapper::class;
3634
}
3735

38-
$passwordChecker = null;
39-
$passwordCheckerParameters = [];
40-
if (!empty($userConfig['password_strength_checker'])) {
41-
if (is_array($userConfig['password_strength_checker'])) {
42-
if (!is_string($userConfig['password_strength_checker']['implementation'] ?? null) || !is_array($userConfig['password_strength_checker']['config'] ?? null)) {
43-
throw new PasswordStrengthCheckerException("When using array notation, the password strength checker must contain 'implementation' and 'config'");
44-
}
45-
$checkerImplementation = new $userConfig['password_strength_checker']['implementation'];
46-
$passwordCheckerParameters = $userConfig['password_strength_checker']['config'];
47-
} else {
48-
$checkerImplementation = new $userConfig['password_strength_checker'];
49-
}
50-
51-
if ($checkerImplementation instanceof PasswordCheckerInterface) {
52-
$passwordChecker = $checkerImplementation;
53-
}
54-
55-
}
56-
5736
return new AuthenticationService(
5837
$container->get($authMapper),
5938
$container->get($userProvider),
6039
$resetTokenProvider ? $container->get($resetTokenProvider) : null,
6140
base64_decode($userConfig['auth']['crypto_key']),
6241
$userConfig['auth']['transient'],
6342
false,
64-
$passwordChecker ?? new PasswordNotChecked(),
65-
$passwordCheckerParameters,
43+
$container->get(PasswordCheckerInterface::class),
6644
$userConfig['password_reset_tokens']['validate_fingerprint'] ?? true,
6745
$userConfig['password_reset_tokens']['validate_ip'] ?? false
6846
);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace CirclicalUser\Factory\Service\PasswordChecker;
4+
5+
use CirclicalUser\Exception\PasswordStrengthCheckerException;
6+
use CirclicalUser\Provider\PasswordCheckerInterface;
7+
use CirclicalUser\Service\PasswordChecker\PasswordNotChecked;
8+
use Interop\Container\ContainerInterface;
9+
use Zend\ServiceManager\Factory\FactoryInterface;
10+
11+
class PasswordCheckerFactory implements FactoryInterface
12+
{
13+
14+
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
15+
{
16+
$config = $container->get('config');
17+
$userConfig = $config['circlical']['user'];
18+
$passwordChecker = null;
19+
if (!empty($userConfig['password_strength_checker'])) {
20+
if (is_array($userConfig['password_strength_checker'])) {
21+
if (!is_string($userConfig['password_strength_checker']['implementation'] ?? null) || !is_array($userConfig['password_strength_checker']['config'] ?? null)) {
22+
throw new PasswordStrengthCheckerException("When using array notation, the password strength checker must contain 'implementation' and 'config'");
23+
}
24+
$checkerImplementation = new $userConfig['password_strength_checker']['implementation']($userConfig['password_strength_checker']['config']);
25+
} else {
26+
$checkerImplementation = new $userConfig['password_strength_checker'];
27+
}
28+
29+
if (!$checkerImplementation instanceof PasswordCheckerInterface) {
30+
throw new \RuntimeException("An invalid type of password checker was specified!");
31+
}
32+
33+
return $checkerImplementation;
34+
}
35+
36+
return new PasswordNotChecked();
37+
}
38+
}
39+

0 commit comments

Comments
 (0)