Updated PHP and Contao requirements
This commit is contained in:
1 parent
bda13251a7
commit
6ee3b2a94d
31 files changed
+297
-458
No files matched your search
@@ -0,0 +1,24 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{php,twig,yml}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{svg,min.css,min.js}]
|
||||
insert_final_newline = false
|
||||
|
||||
[*.html5]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,2 @@
|
||||
github: terminal42
|
||||
ko_fi: terminal42
|
||||
@@ -0,0 +1,29 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push: ~
|
||||
pull_request: ~
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
ecs:
|
||||
name: build-tools
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.1
|
||||
extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, mysqli, pcre, pdo_mysql, zlib
|
||||
coverage: none
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install the dependencies
|
||||
run: |
|
||||
composer install --no-interaction --no-progress
|
||||
|
||||
- name: Run all build-tools to validate code
|
||||
run: composer run build-tools
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
/vendor/
|
||||
composer.lock
|
||||
/composer.lock
|
||||
@@ -1,7 +1,7 @@
|
||||
terminal42/contao-password-validation
|
||||
=====================================
|
||||
|
||||
A Contao 4 bundle that validates user passwords against your password policy.
|
||||
A Contao bundle that validates user passwords against your password policy.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
+18
-16
@@ -11,19 +11,29 @@
|
||||
"homepage": "https://www.terminal42.ch"
|
||||
}
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/terminal42"
|
||||
},
|
||||
{
|
||||
"type": "other",
|
||||
"url": "https://ko-fi.com/terminal42"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/terminal42/contao-password-validation/issues",
|
||||
"source": "https://github.com/terminal42/contao-password-validation"
|
||||
"source": "https://github.com/terminal42/contao-password-validation",
|
||||
"forum": "https://community.contao.org"
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0",
|
||||
"contao/core-bundle": "^4.9",
|
||||
"doctrine/dbal": "^2.11 || ^3",
|
||||
"php": "^8.1",
|
||||
"contao/core-bundle": "^4.13 || ^5.0",
|
||||
"paragonie/hidden-string": "^1.0",
|
||||
"symfony/config": "^4.2 || ^5.0",
|
||||
"symfony/dependency-injection": "^4.0 || ^5.0",
|
||||
"symfony/http-client": "^4.0 || ^5.0",
|
||||
"symfony/http-kernel": "^4.0 || ^5.0"
|
||||
"symfony/config": "^5.0 || ^6.0",
|
||||
"symfony/dependency-injection": "^5.0 || ^6.0",
|
||||
"symfony/http-client": "^5.0 || ^6.0",
|
||||
"symfony/http-kernel": "^5.0 || ^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"contao/manager-plugin": "^2.7",
|
||||
@@ -36,13 +46,5 @@
|
||||
},
|
||||
"extra": {
|
||||
"contao-manager-plugin": "Terminal42\\PasswordValidationBundle\\ContaoManager\\Plugin"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"contao-components/installer": true,
|
||||
"contao/manager-plugin": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"terminal42/contao-build-tools": true
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-23
@@ -1,29 +1,33 @@
|
||||
services:
|
||||
Terminal42\PasswordValidationBundle\EventListener\PasswordRegexpListener:
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_manager'
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@request_stack'
|
||||
Terminal42\PasswordValidationBundle\EventListener\PasswordRegexpListener:
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_manager'
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@request_stack'
|
||||
- '@contao.routing.scope_matcher'
|
||||
- '@security.helper'
|
||||
|
||||
Terminal42\PasswordValidationBundle\EventListener\PasswordHistoryListener:
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration', '@security.encoder_factory', '@database_connection']
|
||||
Terminal42\PasswordValidationBundle\EventListener\PasswordHistoryListener:
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@security.password_hasher_factory'
|
||||
- '@database_connection'
|
||||
|
||||
Terminal42\PasswordValidationBundle\EventListener\PasswordChangeListener:
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration']
|
||||
Terminal42\PasswordValidationBundle\EventListener\PasswordChangeListener:
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
|
||||
Terminal42\PasswordValidationBundle\EventListener\NoPwChangePageWarning:
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration']
|
||||
Terminal42\PasswordValidationBundle\EventListener\NoPwChangePageWarning:
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@translator'
|
||||
|
||||
Terminal42\PasswordValidationBundle\EventListener\RedirectToPasswordChangePageListener:
|
||||
arguments:
|
||||
- '@contao.framework'
|
||||
- '@security.token_storage'
|
||||
- '@security.authentication.trust_resolver'
|
||||
- '@contao.routing.scope_matcher'
|
||||
tags:
|
||||
- kernel.event_listener
|
||||
Terminal42\PasswordValidationBundle\EventListener\RedirectToPasswordChangePageListener:
|
||||
arguments:
|
||||
- '@contao.framework'
|
||||
- '@security.token_storage'
|
||||
- '@contao.routing.scope_matcher'
|
||||
tags:
|
||||
- kernel.event_listener
|
||||
|
||||
Terminal42\PasswordValidationBundle\EventListener\SetNewPasswordListener: ~
|
||||
Terminal42\PasswordValidationBundle\EventListener\SetNewPasswordListener: ~
|
||||
+6
-5
@@ -1,7 +1,8 @@
|
||||
services:
|
||||
terminal42_password_validation.validator_configuration:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration
|
||||
terminal42_password_validation.validator_configuration:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration
|
||||
|
||||
terminal42_password_validation.validator_manager:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\ValidatorManager
|
||||
arguments: [ !tagged_iterator { tag: 'terminal42_password_validation.validator', index_by: 'alias' } ]
|
||||
terminal42_password_validation.validator_manager:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\ValidatorManager
|
||||
arguments:
|
||||
- !tagged_iterator { tag: 'terminal42_password_validation.validator', index_by: 'alias' }
|
||||
+37
-30
@@ -1,35 +1,42 @@
|
||||
services:
|
||||
terminal42_password_validation.validator.password_history:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\PasswordHistory
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: password_history }
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration', '@security.encoder_factory']
|
||||
terminal42_password_validation.validator.password_history:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\PasswordHistory
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: password_history }
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@security.password_hasher_factory'
|
||||
- '@translator'
|
||||
|
||||
terminal42_password_validation.validator.minimum_length:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\MinimumLength
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: minimum_length }
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration']
|
||||
terminal42_password_validation.validator.minimum_length:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\MinimumLength
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: minimum_length }
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@translator'
|
||||
|
||||
terminal42_password_validation.validator.maximum_length:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\MaximumLength
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: maximum_length }
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration']
|
||||
terminal42_password_validation.validator.maximum_length:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\MaximumLength
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: maximum_length }
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@translator'
|
||||
|
||||
terminal42_password_validation.validator.required_characters:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\RequiredCharacters
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: required_characters }
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration']
|
||||
terminal42_password_validation.validator.required_characters:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\RequiredCharacters
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: required_characters }
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@translator'
|
||||
|
||||
terminal42_password_validation.validator.haveibeenpwned:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\HaveIBeenPwned
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: haveibeenpwned }
|
||||
arguments:
|
||||
['@terminal42_password_validation.validator_configuration', '@http_client']
|
||||
terminal42_password_validation.validator.haveibeenpwned:
|
||||
class: Terminal42\PasswordValidationBundle\Validation\Validator\HaveIBeenPwned
|
||||
tags:
|
||||
- { name: terminal42_password_validation.validator, alias: haveibeenpwned }
|
||||
arguments:
|
||||
- '@terminal42_password_validation.validator_configuration'
|
||||
- '@http_client'
|
||||
- '@translator'
|
||||
@@ -11,7 +11,7 @@ $paletteManipulator = PaletteManipulator::create()
|
||||
try {
|
||||
$paletteManipulator->applyToPalette('root', 'tl_page');
|
||||
$paletteManipulator->applyToPalette('rootfallback', 'tl_page');
|
||||
} catch (PaletteNotFoundException $e) {
|
||||
} catch (PaletteNotFoundException) {
|
||||
}
|
||||
|
||||
$GLOBALS['TL_DCA']['tl_page']['fields']['pwChangePage'] = [
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\ContaoManager;
|
||||
|
||||
use Contao\CoreBundle\ContaoCoreBundle;
|
||||
@@ -24,11 +16,7 @@ final class Plugin implements BundlePluginInterface
|
||||
{
|
||||
return [
|
||||
BundleConfig::create(Terminal42PasswordValidationBundle::class)
|
||||
->setLoadAfter(
|
||||
[
|
||||
ContaoCoreBundle::class,
|
||||
]
|
||||
),
|
||||
->setLoadAfter([ContaoCoreBundle::class]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
@@ -20,7 +12,6 @@ final class Configuration implements ConfigurationInterface
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$treeBuilder = new TreeBuilder('terminal42_password_validation');
|
||||
|
||||
$treeBuilder
|
||||
->getRootNode()
|
||||
->useAttributeAsKey('entity')
|
||||
@@ -29,10 +20,10 @@ final class Configuration implements ConfigurationInterface
|
||||
->integerNode('min_length')->end()
|
||||
->integerNode('max_length')->end()
|
||||
->integerNode('invalid_attempts')
|
||||
->setDeprecated('The "%node%" option is not available since Contao 4.9 comes with improved brute-force-protection.')
|
||||
->setDeprecated('terminal42/contao-password-validation', '1.0.8', 'The "%node%" option is not available since Contao 4.9 comes with improved brute-force-protection.')
|
||||
->end()
|
||||
->integerNode('nc_account_disabled')
|
||||
->setDeprecated('The "%node%" option is not available since Contao 4.9 comes with improved brute-force-protection.')
|
||||
->setDeprecated('terminal42/contao-password-validation', '1.0.8', 'The "%node%" option is not available since Contao 4.9 comes with improved brute-force-protection.')
|
||||
->end()
|
||||
->integerNode('password_history')->end()
|
||||
->integerNode('change_days')->end()
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
@@ -19,15 +11,12 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
|
||||
final class Terminal42PasswordValidationExtension extends Extension
|
||||
{
|
||||
private static $files = [
|
||||
private static array $files = [
|
||||
'listeners.yml',
|
||||
'services.yml',
|
||||
'validators.yml',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$config = $this->processConfiguration(new Configuration(), $configs);
|
||||
|
||||
@@ -2,23 +2,16 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\EventListener;
|
||||
|
||||
use Contao\CoreBundle\ServiceAnnotation\Callback;
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\Input;
|
||||
use Contao\MemberModel;
|
||||
use Contao\Message;
|
||||
use Contao\PageModel;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
|
||||
/**
|
||||
@@ -26,16 +19,13 @@ use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
*/
|
||||
final class NoPwChangePageWarning
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
public function __construct(
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @Hook("getSystemMessages")
|
||||
*/
|
||||
#[AsHook('getSystemMessages')]
|
||||
public function onGetSystemMessages(): string
|
||||
{
|
||||
$needsPwChangePage = false;
|
||||
@@ -62,16 +52,14 @@ final class NoPwChangePageWarning
|
||||
|
||||
$rootPages = PageModel::findBy(["tl_page.type='root' AND tl_page.pwChangePage=''"], []);
|
||||
|
||||
if ($needsPwChangePage && null !== $rootPages) {
|
||||
return '<p class="tl_error">'.$this->translate('noPwChangePage').'</p>';
|
||||
if (null !== $rootPages) {
|
||||
return '<p class="tl_error">'.$this->translator->trans('ERR.passwordValidation.noPwChangePage', [], 'contao_default').'</p>';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @Callback(table="tl_page", target="config.onload")
|
||||
*/
|
||||
#[AsCallback(table: 'tl_page', target: 'config.onload')]
|
||||
public function tlPageShowWarning(): void
|
||||
{
|
||||
if (Input::get('act')) {
|
||||
@@ -80,9 +68,4 @@ final class NoPwChangePageWarning
|
||||
|
||||
Message::addRaw($this->onGetSystemMessages());
|
||||
}
|
||||
|
||||
private function translate(string $key)
|
||||
{
|
||||
return $GLOBALS['TL_LANG']['ERR']['passwordValidation'][$key];
|
||||
}
|
||||
}
|
||||
@@ -12,23 +12,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\EventListener;
|
||||
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||
use Contao\User;
|
||||
use Terminal42\PasswordValidationBundle\Model\PasswordHistory;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
|
||||
/**
|
||||
* This listener forces a password change for passwords being too old.
|
||||
*
|
||||
* @Hook("postLogin")
|
||||
*/
|
||||
#[AsHook('postLogin')]
|
||||
final class PasswordChangeListener
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration)
|
||||
public function __construct(private readonly ValidationConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function __invoke(User $user): void
|
||||
@@ -38,10 +34,12 @@ final class PasswordChangeListener
|
||||
}
|
||||
|
||||
$userId = (int) $user->id;
|
||||
$passwordLog = PasswordHistory::findCurrentLog(\get_class($user), $userId);
|
||||
$passwordLog = PasswordHistory::findCurrentLog($user::class, $userId);
|
||||
|
||||
if (null === $passwordLog) {
|
||||
if (!$passwordLog) {
|
||||
$this->forcePasswordChange($user);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$maxAge = strtotime("-$maxDays days");
|
||||
@@ -51,9 +49,9 @@ final class PasswordChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
private function getMaxDays(User $user): ?int
|
||||
private function getMaxDays(User $user): int|null
|
||||
{
|
||||
$userEntity = \get_class($user);
|
||||
$userEntity = $user::class;
|
||||
|
||||
if (false === $this->configuration->hasConfiguration($userEntity)) {
|
||||
return null;
|
||||
@@ -72,7 +70,7 @@ final class PasswordChangeListener
|
||||
|
||||
private function forcePasswordChange(User $user): void
|
||||
{
|
||||
$user->pwChange = 1;
|
||||
$user->pwChange = true;
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\EventListener;
|
||||
|
||||
use Contao\BackendUser;
|
||||
use Contao\CoreBundle\ServiceAnnotation\Callback;
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||
use Contao\Database\Result as DatabaseResult;
|
||||
use Contao\DataContainer;
|
||||
use Contao\FrontendUser;
|
||||
@@ -22,7 +14,7 @@ use Contao\MemberModel;
|
||||
use Contao\StringUtil;
|
||||
use Contao\User;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
use Terminal42\PasswordValidationBundle\Model\PasswordHistory;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
|
||||
@@ -31,27 +23,20 @@ use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
*/
|
||||
final class PasswordHistoryListener
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
private $encoderFactory;
|
||||
|
||||
private $connection;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration, EncoderFactoryInterface $encoderFactory, Connection $connection)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->encoderFactory = $encoderFactory;
|
||||
$this->connection = $connection;
|
||||
public function __construct(
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly PasswordHasherFactoryInterface $encoderFactory,
|
||||
private readonly Connection $connection,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook is triggered for frontend users exclusively (ModuleChangePassword and save_callback).
|
||||
*
|
||||
* @Hook("setNewPassword")
|
||||
*
|
||||
* @param MemberModel|DatabaseResult $member
|
||||
*/
|
||||
public function onSetNewPassword($member, string $password): void
|
||||
#[AsHook('setNewPassword')]
|
||||
public function onSetNewPassword($member, #[\SensitiveParameter] string $password): void
|
||||
{
|
||||
if (false === $this->configuration->hasConfiguration(FrontendUser::class)) {
|
||||
return;
|
||||
@@ -96,10 +81,9 @@ final class PasswordHistoryListener
|
||||
|
||||
/**
|
||||
* This listener keeps track of the backend user's passwords.
|
||||
*
|
||||
* @Callback(table="tl_user", target="fields.password.save")
|
||||
*/
|
||||
public function onBackendSaveCallback(string $password, DataContainer $dc): string
|
||||
#[AsCallback(table: 'tl_user', target: 'fields.password.save')]
|
||||
public function onBackendSaveCallback(#[\SensitiveParameter] string $password, DataContainer $dc): string
|
||||
{
|
||||
if (false === $this->configuration->hasConfiguration(BackendUser::class)) {
|
||||
return $password;
|
||||
@@ -115,7 +99,7 @@ final class PasswordHistoryListener
|
||||
$hash = $password;
|
||||
|
||||
if (!$this->isHashedPassword($hash)) {
|
||||
$hash = $this->hashPassword($hash);
|
||||
$hash = $this->encoderFactory->getPasswordHasher(User::class)->hash($hash);
|
||||
}
|
||||
|
||||
$userId = (int) $dc->id;
|
||||
@@ -125,13 +109,8 @@ final class PasswordHistoryListener
|
||||
return $password;
|
||||
}
|
||||
|
||||
private function isHashedPassword(string $password): bool
|
||||
private function isHashedPassword(#[\SensitiveParameter] string $password): bool
|
||||
{
|
||||
return (bool) password_get_info($password)['algo'];
|
||||
}
|
||||
|
||||
private function hashPassword(string $password): string
|
||||
{
|
||||
return $this->encoderFactory->getEncoder(User::class)->encodePassword($password, null);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\EventListener;
|
||||
|
||||
use Contao\BackendUser;
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||
use Contao\CoreBundle\Routing\ScopeMatcher;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\Input;
|
||||
use Contao\ModulePersonalData;
|
||||
@@ -21,6 +14,7 @@ use Contao\ModuleRegistration;
|
||||
use Contao\Widget;
|
||||
use ParagonIE\HiddenString\HiddenString;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationContext;
|
||||
@@ -28,33 +22,27 @@ use Terminal42\PasswordValidationBundle\Validation\ValidatorManager;
|
||||
|
||||
/**
|
||||
* This listener validates the password input by providing a regexp.
|
||||
*
|
||||
* @Hook("addCustomRegexp")
|
||||
*/
|
||||
#[AsHook('addCustomRegexp')]
|
||||
final class PasswordRegexpListener
|
||||
{
|
||||
private $validatorManager;
|
||||
|
||||
private $configuration;
|
||||
/**
|
||||
* @var RequestStack
|
||||
*/
|
||||
private $requestStack;
|
||||
|
||||
public function __construct(ValidatorManager $validatorManager, ValidationConfiguration $configuration, RequestStack $requestStack)
|
||||
{
|
||||
$this->validatorManager = $validatorManager;
|
||||
$this->configuration = $configuration;
|
||||
$this->requestStack = $requestStack;
|
||||
public function __construct(
|
||||
private readonly ValidatorManager $validatorManager,
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly RequestStack $requestStack,
|
||||
private readonly ScopeMatcher $scopeMatcher,
|
||||
private readonly Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(string $rgxp, $input, Widget $widget): bool
|
||||
public function __invoke(string $rgxp, #[\SensitiveParameter] mixed $input, Widget $widget): bool
|
||||
{
|
||||
if ('terminal42_password_validation' !== $rgxp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dc = $widget->dataContainer;
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if ($dc instanceof ModulePersonalData) {
|
||||
$userId = (int) FrontendUser::getInstance()->id;
|
||||
@@ -62,11 +50,12 @@ final class PasswordRegexpListener
|
||||
} elseif ($dc instanceof ModuleRegistration) {
|
||||
$userId = null;
|
||||
$userEntity = FrontendUser::class;
|
||||
} elseif ('FE' === TL_MODE && FE_USER_LOGGED_IN) {
|
||||
} elseif ($request && $this->scopeMatcher->isFrontendRequest($request) && $this->security->isGranted('ROLE_MEMBER')) {
|
||||
$userId = (int) FrontendUser::getInstance()->id;
|
||||
$userEntity = FrontendUser::class;
|
||||
} elseif (
|
||||
'FE' === TL_MODE
|
||||
$request
|
||||
&& $this->scopeMatcher->isFrontendRequest($request)
|
||||
&& Input::post('FORM_SUBMIT') === $this->requestStack->getSession()->get('setPasswordToken')
|
||||
&& $widget->currentRecord
|
||||
) {
|
||||
@@ -106,9 +95,9 @@ final class PasswordRegexpListener
|
||||
$widget->addError($e->getMessage());
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
// Unhandled exceptions can be dangerous since the plaintext password is passed to this method.
|
||||
// The password would have been available in the stack trace.
|
||||
} catch (\Throwable) {
|
||||
// Unhandled exceptions can be dangerous since the plaintext password is passed
|
||||
// to this method. The password would have been available in the stack trace.
|
||||
$widget->addError('An unexpected error occurred.');
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\EventListener;
|
||||
|
||||
use Contao\CoreBundle\Exception\PageNotFoundException;
|
||||
@@ -19,50 +11,28 @@ use Contao\CoreBundle\Routing\ScopeMatcher;
|
||||
use Contao\FrontendUser;
|
||||
use Contao\PageModel;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class RedirectToPasswordChangePageListener
|
||||
{
|
||||
private $framework;
|
||||
|
||||
private $tokenStorage;
|
||||
|
||||
private $authenticationTrustResolver;
|
||||
|
||||
private $scopeMatcher;
|
||||
|
||||
public function __construct(ContaoFramework $framework, TokenStorageInterface $tokenStorage, AuthenticationTrustResolverInterface $authenticationTrustResolver, ScopeMatcher $scopeMatcher)
|
||||
{
|
||||
$this->framework = $framework;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->authenticationTrustResolver = $authenticationTrustResolver;
|
||||
$this->scopeMatcher = $scopeMatcher;
|
||||
public function __construct(
|
||||
private readonly ContaoFramework $framework,
|
||||
private readonly Security $security,
|
||||
private readonly ScopeMatcher $scopeMatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(RequestEvent $event): void
|
||||
{
|
||||
if (!$this->scopeMatcher->isFrontendMasterRequest($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->tokenStorage->getToken();
|
||||
|
||||
if (null === $token || $this->authenticationTrustResolver->isAnonymous($token)) {
|
||||
if (!$this->scopeMatcher->isFrontendMainRequest($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = $event->getRequest();
|
||||
$user = $this->security->getUser();
|
||||
$page = $request->attributes->get('pageModel');
|
||||
|
||||
// Check if actual page is available
|
||||
if (!$page instanceof PageModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof FrontendUser) {
|
||||
if (!$page instanceof PageModel || !$user instanceof FrontendUser || !$user->pwChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,12 +40,8 @@ final class RedirectToPasswordChangePageListener
|
||||
$adapter = $this->framework->getAdapter(PageModel::class);
|
||||
$rootPage = $adapter->findByPk($page->rootId);
|
||||
|
||||
if (!$user->pwChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for password-change page
|
||||
$pwChangePage = $adapter->findPublishedById($rootPage->pwChangePage);
|
||||
$pwChangePage = $adapter->findPublishedById($rootPage->pwChangePage ?? 0);
|
||||
|
||||
if (!$pwChangePage instanceof PageModel) {
|
||||
throw new PageNotFoundException('No password-change page found.');
|
||||
|
||||
@@ -2,29 +2,24 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\EventListener;
|
||||
|
||||
use Contao\CoreBundle\ServiceAnnotation\Hook;
|
||||
use Contao\CoreBundle\DependencyInjection\Attribute\AsHook;
|
||||
use Contao\Database\Result;
|
||||
use Contao\MemberModel;
|
||||
|
||||
/**
|
||||
* @Hook("setNewPassword")
|
||||
*/
|
||||
#[AsHook('setNewPassword')]
|
||||
final class SetNewPasswordListener
|
||||
{
|
||||
/**
|
||||
* @param MemberModel|Result|object $member
|
||||
*/
|
||||
public function __invoke($member): void
|
||||
{
|
||||
if ($member instanceof MemberModel) {
|
||||
// We only want to reset "pwChange" if the user changed their password with the change-password module.
|
||||
// If this hook is called within the save_callback (backend mask), $member is an instance of Database\Result.
|
||||
// We only want to reset "pwChange" if the user changed their password with the
|
||||
// change-password module. If this hook is called within the save_callback
|
||||
// (backend mask), $member is an instance of Database\Result.
|
||||
$member->pwChange = '';
|
||||
$member->save();
|
||||
}
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Exception;
|
||||
|
||||
class PasswordValidatorException extends \Exception
|
||||
|
||||
@@ -19,7 +19,7 @@ final class PasswordHistory extends Model
|
||||
{
|
||||
protected static $strTable = 'tl_password_history';
|
||||
|
||||
public static function addLog(string $entity, int $userId, string $hashedPassword): void
|
||||
public static function addLog(string $entity, int $userId, #[\SensitiveParameter] string $hashedPassword): void
|
||||
{
|
||||
$log = new self();
|
||||
|
||||
@@ -31,7 +31,10 @@ final class PasswordHistory extends Model
|
||||
$log->save();
|
||||
}
|
||||
|
||||
public static function findHistory(string $entity, int $userId, int $length = 10, int $offset = 0): ?Collection
|
||||
/**
|
||||
* @return Collection<PasswordHistory>|null
|
||||
*/
|
||||
public static function findHistory(string $entity, int $userId, int $length = 10, int $offset = 0): Collection|null
|
||||
{
|
||||
return self::findBy(
|
||||
[
|
||||
@@ -47,11 +50,11 @@ final class PasswordHistory extends Model
|
||||
'limit' => $length,
|
||||
'offset' => $offset,
|
||||
'return' => 'Collection',
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public static function findCurrentLog(string $entity, int $userId): ?Model
|
||||
public static function findCurrentLog(string $entity, int $userId): self|null
|
||||
{
|
||||
return self::findOneBy(
|
||||
[
|
||||
@@ -64,7 +67,7 @@ final class PasswordHistory extends Model
|
||||
],
|
||||
[
|
||||
'order' => 'tstamp DESC',
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,7 +76,6 @@ final class PasswordHistory extends Model
|
||||
$history = self::findHistory($entity, $userId, 0, $lengthToKeep);
|
||||
|
||||
if (null !== $history) {
|
||||
/** @var PasswordHistory $log */
|
||||
foreach ($history as $log) {
|
||||
$log->delete();
|
||||
}
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Validation;
|
||||
|
||||
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
|
||||
|
||||
@@ -32,7 +32,7 @@ final class ValidationConfiguration
|
||||
return \array_key_exists($entityName, $this->configurations);
|
||||
}
|
||||
|
||||
public function getConfiguration(string $entityName): ?array
|
||||
public function getConfiguration(string $entityName): array|null
|
||||
{
|
||||
return $this->configurations[$entityName];
|
||||
}
|
||||
|
||||
@@ -21,15 +21,11 @@ use ParagonIE\HiddenString\HiddenString;
|
||||
*/
|
||||
final class ValidationContext
|
||||
{
|
||||
private $userEntity;
|
||||
private $userId;
|
||||
private $password;
|
||||
|
||||
public function __construct(string $userEntity, ?int $userId, HiddenString $password)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
$this->password = $password;
|
||||
$this->userEntity = $userEntity;
|
||||
public function __construct(
|
||||
private readonly string $userEntity,
|
||||
private readonly int|null $userId,
|
||||
private readonly HiddenString $password,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isFrontendUser(): bool
|
||||
@@ -42,7 +38,7 @@ final class ValidationContext
|
||||
return BackendUser::class === $this->userEntity;
|
||||
}
|
||||
|
||||
public function getUserId(): ?int
|
||||
public function getUserId(): int|null
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Validation\Validator;
|
||||
|
||||
use Contao\System;
|
||||
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
|
||||
use Terminal42\PasswordValidationBundle\Validation\PasswordValidatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
@@ -22,13 +14,11 @@ use Terminal42\PasswordValidationBundle\Validation\ValidationContext;
|
||||
|
||||
final class HaveIBeenPwned implements PasswordValidatorInterface
|
||||
{
|
||||
private $configuration;
|
||||
private $client;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration, HttpClientInterface $client)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->client = $client;
|
||||
public function __construct(
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly HttpClientInterface $client,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(ValidationContext $context): bool
|
||||
@@ -50,7 +40,7 @@ final class HaveIBeenPwned implements PasswordValidatorInterface
|
||||
|
||||
try {
|
||||
$response = $this->client->request('GET', 'https://api.pwnedpasswords.com/range/'.$hash05)->getContent();
|
||||
} catch (HttpExceptionInterface $e) {
|
||||
} catch (HttpExceptionInterface) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -62,20 +52,13 @@ final class HaveIBeenPwned implements PasswordValidatorInterface
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
if (\array_key_exists($hash, $breaches) && ($quantity = $breaches[$hash]) && $quantity > $minDataBreaches) {
|
||||
throw new PasswordValidatorException(sprintf($this->translate('haveibeenpwned'), $quantity));
|
||||
throw new PasswordValidatorException($this->translator->trans('XPT.passwordValidation.haveibeenpwned', [$quantity], 'contao_exception'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function translate(string $key)
|
||||
{
|
||||
System::loadLanguageFile('exception');
|
||||
|
||||
return $GLOBALS['TL_LANG']['XPT']['passwordValidation'][$key];
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Validation\Validator;
|
||||
|
||||
use Contao\System;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
|
||||
use Terminal42\PasswordValidationBundle\Validation\PasswordValidatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
@@ -20,11 +12,10 @@ use Terminal42\PasswordValidationBundle\Validation\ValidationContext;
|
||||
|
||||
final class MaximumLength implements PasswordValidatorInterface
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
public function __construct(
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(ValidationContext $context): bool
|
||||
@@ -43,16 +34,9 @@ final class MaximumLength implements PasswordValidatorInterface
|
||||
$password = $context->getPassword()->getString();
|
||||
|
||||
if (\strlen($password) > $maximumLength) {
|
||||
throw new PasswordValidatorException(sprintf($this->translate('maxLength'), $maximumLength));
|
||||
throw new PasswordValidatorException($this->translator->trans('XPT.passwordValidation.maxLength', [$maximumLength], 'contao_exception'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function translate(string $key)
|
||||
{
|
||||
System::loadLanguageFile('exception');
|
||||
|
||||
return $GLOBALS['TL_LANG']['XPT']['passwordValidation'][$key];
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Validation\Validator;
|
||||
|
||||
use Contao\System;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
|
||||
use Terminal42\PasswordValidationBundle\Validation\PasswordValidatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
@@ -20,11 +12,10 @@ use Terminal42\PasswordValidationBundle\Validation\ValidationContext;
|
||||
|
||||
final class MinimumLength implements PasswordValidatorInterface
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
public function __construct(
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(ValidationContext $context): bool
|
||||
@@ -43,16 +34,9 @@ final class MinimumLength implements PasswordValidatorInterface
|
||||
$password = $context->getPassword()->getString();
|
||||
|
||||
if (\strlen($password) < $minimumLength) {
|
||||
throw new PasswordValidatorException(sprintf($this->translate('minLength'), $minimumLength));
|
||||
throw new PasswordValidatorException($this->translator->trans('XPT.passwordValidation.minLength', [$minimumLength], 'contao_exception'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function translate(string $key)
|
||||
{
|
||||
System::loadLanguageFile('exception');
|
||||
|
||||
return $GLOBALS['TL_LANG']['XPT']['passwordValidation'][$key];
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Validation\Validator;
|
||||
|
||||
use Contao\System;
|
||||
use Contao\User;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
|
||||
use Terminal42\PasswordValidationBundle\Model\PasswordHistory as PasswordHistoryModel;
|
||||
use Terminal42\PasswordValidationBundle\Validation\PasswordValidatorInterface;
|
||||
@@ -23,14 +15,11 @@ use Terminal42\PasswordValidationBundle\Validation\ValidationContext;
|
||||
|
||||
final class PasswordHistory implements PasswordValidatorInterface
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
private $encoderFactory;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration, EncoderFactoryInterface $encoderFactory)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->encoderFactory = $encoderFactory;
|
||||
public function __construct(
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly PasswordHasherFactoryInterface $encoderFactory,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(ValidationContext $context): bool
|
||||
@@ -62,27 +51,11 @@ final class PasswordHistory implements PasswordValidatorInterface
|
||||
|
||||
/** @var PasswordHistoryModel $log */
|
||||
foreach ($history as $log) {
|
||||
if ($this->verifyPassword((string) $log->password, $password)) {
|
||||
throw new PasswordValidatorException($this->translate('passwordHistory'));
|
||||
if ($this->encoderFactory->getPasswordHasher(User::class)->verify((string) $log->password, $password)) {
|
||||
throw new PasswordValidatorException($this->translator->trans('XPT.passwordValidation.passwordHistory', [], 'contao_exception'));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function verifyPassword(string $hashedPassword, string $password)
|
||||
{
|
||||
if (version_compare(VERSION, '4.8', '>=')) {
|
||||
return $this->encoderFactory->getEncoder(User::class)->isPasswordValid($hashedPassword, $password, null);
|
||||
}
|
||||
|
||||
return password_verify($password, $hashedPassword);
|
||||
}
|
||||
|
||||
private function translate(string $key)
|
||||
{
|
||||
System::loadLanguageFile('exception');
|
||||
|
||||
return $GLOBALS['TL_LANG']['XPT']['passwordValidation'][$key];
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Validation\Validator;
|
||||
|
||||
use Contao\StringUtil;
|
||||
use Contao\System;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
|
||||
use Terminal42\PasswordValidationBundle\Validation\PasswordValidatorInterface;
|
||||
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
|
||||
@@ -21,11 +20,10 @@ use Terminal42\PasswordValidationBundle\Validation\ValidationContext;
|
||||
|
||||
final class RequiredCharacters implements PasswordValidatorInterface
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
public function __construct(ValidationConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
public function __construct(
|
||||
private readonly ValidationConfiguration $configuration,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(ValidationContext $context): bool
|
||||
@@ -59,17 +57,27 @@ final class RequiredCharacters implements PasswordValidatorInterface
|
||||
if ($actual < $minimum) {
|
||||
if ('other' === $category) {
|
||||
$errors[] = new PasswordValidatorException(
|
||||
sprintf($this->translate('required.other'), $minimum, $configuration['other_chars'] ?? null)
|
||||
$this->translator->trans(
|
||||
'XPT.passwordValidation.required.other',
|
||||
[$minimum, $configuration['other_chars'] ?? ''],
|
||||
'contao_exception',
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$errors[] = new PasswordValidatorException(sprintf($this->translate('required.'.$category), $minimum));
|
||||
$errors[] = new PasswordValidatorException(
|
||||
$this->translator->trans(
|
||||
'XPT.passwordValidation.required.'.$category,
|
||||
[$minimum],
|
||||
'contao_exception',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($errors) > 1) {
|
||||
throw new PasswordValidatorException(sprintf($this->translate('required.summary'), $require['uppercase'] ?? null, $require['lowercase'] ?? null, $require['numbers'] ?? null, $require['other'] ?? null, $configuration['other_chars'] ?? null));
|
||||
throw new PasswordValidatorException($this->translator->trans('XPT.passwordValidation.required.summary', [$require['uppercase'] ?? 0, $require['lowercase'] ?? 0, $require['numbers'] ?? 0, $require['other'] ?? 0, $configuration['other_chars'] ?? ''], 'contao_exception'));
|
||||
}
|
||||
|
||||
if (\count($errors) > 0) {
|
||||
@@ -79,7 +87,7 @@ final class RequiredCharacters implements PasswordValidatorInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
private function countRequirement(string $category, string $string, ValidationContext $context): ?int
|
||||
private function countRequirement(string $category, string $string, ValidationContext $context): int|null
|
||||
{
|
||||
switch ($category) {
|
||||
case 'lowercase':
|
||||
@@ -93,7 +101,7 @@ final class RequiredCharacters implements PasswordValidatorInterface
|
||||
return \strlen($lowercase) - similar_text($string, $lowercase);
|
||||
|
||||
case 'numbers':
|
||||
return \strlen(preg_replace('/\D+/', '', $string));
|
||||
return \strlen((string) preg_replace('/\D+/', '', $string));
|
||||
|
||||
case 'other':
|
||||
$chars = $this->getRequiredOtherCharactersForRegexp($context);
|
||||
@@ -102,14 +110,14 @@ final class RequiredCharacters implements PasswordValidatorInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
return \strlen(preg_replace('/[^'.$chars.']+/', '', $string));
|
||||
return \strlen((string) preg_replace('/[^'.$chars.']+/', '', $string));
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getRequiredOtherCharactersForRegexp(ValidationContext $context): ?string
|
||||
private function getRequiredOtherCharactersForRegexp(ValidationContext $context): string|null
|
||||
{
|
||||
$config = $this->configuration->getConfiguration($context->getUserEntity());
|
||||
$chars = $config['other_chars'] ?? null;
|
||||
@@ -120,19 +128,10 @@ final class RequiredCharacters implements PasswordValidatorInterface
|
||||
|
||||
$return = '';
|
||||
|
||||
foreach (array_unique(preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY)) as $char) {
|
||||
foreach (array_unique(preg_split('//u', (string) $chars, -1, PREG_SPLIT_NO_EMPTY)) as $char) {
|
||||
$return .= '\\'.$char;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function translate(string $key)
|
||||
{
|
||||
System::loadLanguageFile('exception');
|
||||
|
||||
[$key1, $key2] = StringUtil::trimsplit('.', $key);
|
||||
|
||||
return $GLOBALS['TL_LANG']['XPT']['passwordValidation'][$key1][$key2];
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of terminal42/contao-password-validation.
|
||||
*
|
||||
* (c) terminal42 gmbh <https://terminal42.ch>
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Terminal42\PasswordValidationBundle\Validation;
|
||||
|
||||
/**
|
||||
@@ -18,9 +10,9 @@ namespace Terminal42\PasswordValidationBundle\Validation;
|
||||
final class ValidatorManager
|
||||
{
|
||||
/**
|
||||
* @var array<PasswordValidatorInterface>
|
||||
* @var array<string, PasswordValidatorInterface>
|
||||
*/
|
||||
private $validators = [];
|
||||
private array $validators;
|
||||
|
||||
public function __construct(iterable $validators)
|
||||
{
|
||||
@@ -32,12 +24,15 @@ final class ValidatorManager
|
||||
$this->validators[$alias] = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getValidatorNames(): array
|
||||
{
|
||||
return array_keys($this->validators);
|
||||
}
|
||||
|
||||
public function getValidator($name): ?PasswordValidatorInterface
|
||||
public function getValidator(string $name): PasswordValidatorInterface|null
|
||||
{
|
||||
return $this->validators[$name] ?? null;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user