Add a "have i been pwned" validator

This commit is contained in:
Richard Henkenjohann committed 2022-06-26 16:31:08 +02:00
1 parent cc650a19b7
commit eb0d5d800b
7 files changed
+101 -1

No files matched your search

+3
View File
@@ -54,14 +54,17 @@ terminal42_password_validation:
other_chars: "+*ç%&/()=?"
password_history: 10
change_days: 90
haveibeenpwned: 1
Contao\BackendUser:
min_length: 10
haveibeenpwned: 1
```
Parameter | Purpose
--------- | -------
`password_history`: | Keep track of the latest `n` passwords, and force the users not to choose one of their recent passwords.
`change_days`: | Ask the user to change their password after certain days.
`haveibeenpwned`: | Check the user password against known data breaches reported to [';--have i been pwned?](https://haveibeenpwned.com/). The configuration allows you to specify an integer to define the [minimum number of data breaches](https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange) the password needs to occur in to fail password validation.
### Password-change
+3 -1
View File
@@ -27,10 +27,12 @@
"require": {
"php": ">=7.1",
"contao/core-bundle": "^4.9",
"guzzlehttp/guzzle": "^6.2",
"paragonie/hidden-string": "^1.0",
"symfony/config": "^4.0 || ^5.0",
"symfony/dependency-injection": "^4.0 || ^5.0",
"symfony/http-kernel": "^4.0 || ^5.0"
"symfony/http-kernel": "^4.0 || ^5.0",
"terminal42/notification_center": "^1.3"
},
"require-dev": {
"contao/manager-plugin": "^2.7",
@@ -43,6 +43,7 @@ final class Configuration implements ConfigurationInterface
->end()
->integerNode('password_history')->end()
->integerNode('change_days')->end()
->integerNode('haveibeenpwned')->end()
->scalarNode('other_chars')->end()
->arrayNode('require')
->children()
+7
View File
@@ -26,3 +26,10 @@ services:
- { name: terminal42_password_validation.validator, alias: required_characters }
arguments:
['@terminal42_password_validation.validator_configuration']
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']
@@ -35,6 +35,10 @@
<source>Please use at least %u uppercase letters, %u lowercase letters, %u numbers and %u special characters: %s</source>
<target>Bitte verwenden Sie mindestens %u Großbuchstaben, %u Kleinbuchstaben, %u Ziffern und %u Sonderzeichen: %s</target>
</trans-unit>
<trans-unit id="XPT.passwordValidation.haveibeenpwned">
<source>Oh no! Your password was found in a database of compromised passwords! Using a password that has been breached is extremely dangerous. Please choose a different password.</source>
<target>Oh nein! Ihr Passwort wurde in einer Datenbank mit veröffentlichten Passwörtern gefunden! Die Verwendung eines Passworts aus einem Datenleck ist äußerst gefährlich. Bitte wählen Sie ein anderes Passwort.</target>
</trans-unit>
</body>
</file>
</xliff>
@@ -26,6 +26,9 @@
<trans-unit id="XPT.passwordValidation.required.summary">
<source>Please use at least %u uppercase letters, %u lowercase letters, %u numbers and %u special characters: %s</source>
</trans-unit>
<trans-unit id="XPT.passwordValidation.haveibeenpwned">
<source>Oh no! Your password was found in a database of compromised passwords! Using a password that has been breached is extremely dangerous. Please choose a different password.</source>
</trans-unit>
</body>
</file>
</xliff>
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* Password Validation Bundle for Contao Open Source CMS.
*
* @copyright Copyright (c) 2019, terminal42 gmbh
* @author terminal42 <https://terminal42.ch>
* @license MIT
* @link http://github.com/terminal42/contao-password-validation
*/
namespace Terminal42\PasswordValidationBundle\Validation\Validator;
use Contao\System;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Terminal42\PasswordValidationBundle\Exception\PasswordValidatorException;
use Terminal42\PasswordValidationBundle\Validation\PasswordValidatorInterface;
use Terminal42\PasswordValidationBundle\Validation\ValidationConfiguration;
use Terminal42\PasswordValidationBundle\Validation\ValidationContext;
final class HaveIBeenPwned implements PasswordValidatorInterface
{
private $configuration;
public function __construct(ValidationConfiguration $configuration)
{
$this->configuration = $configuration;
}
public function validate(ValidationContext $context): bool
{
if (false === $this->configuration->hasConfiguration($context->getUserEntity())) {
return true;
}
$configuration = $this->configuration->getConfiguration($context->getUserEntity());
$minDataBreaches = $configuration['haveibeenpwned'];
if (!$minDataBreaches) {
return true;
}
$password = $context->getPassword()->getString();
$hash = strtoupper(sha1($password));
$hash05 = substr($hash, 0, 5);
$client = new Client();
try {
$res = $client->request('GET', 'https://api.pwnedpasswords.com/range/'.$hash05);
} catch (GuzzleException $e) {
return true;
}
$breaches = array_reduce(
preg_split("/\r\n|\n|\r/", (string) $res->getBody()),
static function ($carry, $item) use ($hash05) {
[$hash35, $quantity] = explode(':', $item);
$carry[$hash05.$hash35] = $quantity;
return $carry;
},
[]
);
if (\array_key_exists($hash, $breaches) && ($quantity = $breaches[$hash]) && $quantity > $minDataBreaches) {
throw new PasswordValidatorException(sprintf($this->translate('haveibeenpwned'), $quantity));
}
return true;
}
private function translate(string $key)
{
System::loadLanguageFile('exception');
return $GLOBALS['TL_LANG']['XPT']['passwordValidation'][$key];
}
}