Updated PHP and Contao requirements

This commit is contained in:
Andreas Schempp committed 2024-03-04 11:57:34 +01:00
1 parent bda13251a7
commit 6ee3b2a94d
31 files changed
+297 -458

No files matched your search

+24
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
github: terminal42
ko_fi: terminal42
+29
View File
@@ -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
View File
@@ -1,2 +1,2 @@
/vendor/
composer.lock
/composer.lock
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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'
+1 -1
View File
@@ -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'] = [
+1 -13
View File
@@ -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 -11
View File
@@ -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);
+11 -28
View File
@@ -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];
}
}
+10 -12
View File
@@ -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();
}
}
+14 -35
View File
@@ -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);
}
}
+19 -30
View File
@@ -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.');
+9 -14
View File
@@ -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
+8 -6
View File
@@ -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;
+1 -1
View File
@@ -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];
}
+6 -10
View File
@@ -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;
}
+9 -26
View File
@@ -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];
}
}
+6 -22
View File
@@ -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];
}
}
+6 -22
View File
@@ -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];
}
}
+9 -36
View File
@@ -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];
}
}
+23 -24
View File
@@ -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];
}
}
+6 -11
View File
@@ -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;
}