Skip to content

Commit

Permalink
Merge pull request #54 from Saeven/feature/password-validator
Browse files Browse the repository at this point in the history
Feature/password validator
  • Loading branch information
Saeven authored Nov 27, 2020
2 parents f95d2f9 + 08d2249 commit a488508
Show file tree
Hide file tree
Showing 17 changed files with 265 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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 = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' => [
Expand Down Expand Up @@ -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' => [
Expand Down Expand Up @@ -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' => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@
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;
use Zend\ServiceManager\ServiceManager;

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');
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Spec\CirclicalUser\Factory\Service\PasswordChecker;

use CirclicalUser\Exception\PasswordStrengthCheckerException;
use CirclicalUser\Factory\Service\PasswordChecker\PasswordCheckerFactory;
use CirclicalUser\Mapper\RoleMapper;
use CirclicalUser\Provider\PasswordCheckerInterface;
use CirclicalUser\Service\PasswordChecker\PasswordNotChecked;
use CirclicalUser\Service\PasswordChecker\Zxcvbn;
use Interop\Container\ContainerInterface;
use PhpSpec\ObjectBehavior;

class PasswordCheckerFactorySpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->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,
[],
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand Down Expand Up @@ -455,7 +454,6 @@ public function it_will_create_new_auth_records_with_strong_passwords($authentic
false,
false,
new Passwdqc(),
[],
true,
true
);
Expand All @@ -480,7 +478,6 @@ public function it_wont_create_new_auth_records_with_weak_passwords($authenticat
false,
false,
new Passwdqc(),
[],
true,
true
);
Expand All @@ -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
);
Expand Down Expand Up @@ -566,7 +562,6 @@ public function it_fails_to_create_tokens_when_password_changes_are_prohibited($
false,
false,
new PasswordNotChecked(),
[],
true,
true
);
Expand All @@ -583,7 +578,6 @@ public function it_bails_on_password_changes_if_no_provider_is_set($authenticati
false,
false,
new PasswordNotChecked(),
[],
true,
true
);
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -91,13 +95,20 @@
UserEntityListener::class => UserEntityListenerFactory::class,
UserMapper::class => UserMapperFactory::class,
RedirectStrategy::class => RedirectStrategyFactory::class,
PasswordCheckerInterface::class => PasswordCheckerFactory::class,
],

'abstract_factories' => [
AbstractDoctrineMapperFactory::class,
],
],

'validators' => [
'factories' => [
PasswordValidator::class => PasswordValidatorFactory::class,
],
],

'view_helpers' => [
'aliases' => [
'canAccessController' => ControllerAccessViewHelper::class,
Expand Down
8 changes: 1 addition & 7 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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.#'
ignoreErrors:
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -35,34 +33,14 @@ 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),
$resetTokenProvider ? $container->get($resetTokenProvider) : null,
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
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace CirclicalUser\Factory\Service\PasswordChecker;

use CirclicalUser\Exception\PasswordStrengthCheckerException;
use CirclicalUser\Provider\PasswordCheckerInterface;
use CirclicalUser\Service\PasswordChecker\PasswordNotChecked;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;

class PasswordCheckerFactory implements FactoryInterface
{

public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$config = $container->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();
}
}

Loading

0 comments on commit a488508

Please sign in to comment.