[Validator] Make API endpoint for NotCompromisedPasswordValidator configurable
This commit is contained in:
parent
b09dfd9d8e
commit
f6a80c214d
@ -835,9 +835,18 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->booleanNode('disable_not_compromised_password')
|
||||
->defaultFalse()
|
||||
->info('Disable NotCompromisedPassword Validator: the value will always be valid.')
|
||||
->arrayNode('not_compromised_password')
|
||||
->canBeDisabled()
|
||||
->children()
|
||||
->booleanNode('enabled')
|
||||
->defaultTrue()
|
||||
->info('When disabled, compromised passwords will be accepted as valid.')
|
||||
->end()
|
||||
->scalarNode('endpoint')
|
||||
->defaultNull()
|
||||
->info('API endpoint for the NotCompromisedPassword Validator.')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('auto_mapping')
|
||||
->useAttributeAsKey('namespace')
|
||||
|
@ -1249,7 +1249,8 @@ class FrameworkExtension extends Extension
|
||||
|
||||
$container
|
||||
->getDefinition('validator.not_compromised_password')
|
||||
->setArgument(2, $config['disable_not_compromised_password'])
|
||||
->setArgument(2, $config['not_compromised_password']['enabled'])
|
||||
->setArgument(3, $config['not_compromised_password']['endpoint'])
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -235,7 +235,10 @@ class ConfigurationTest extends TestCase
|
||||
'paths' => [],
|
||||
],
|
||||
'auto_mapping' => [],
|
||||
'disable_not_compromised_password' => false,
|
||||
'not_compromised_password' => [
|
||||
'enabled' => true,
|
||||
'endpoint' => null,
|
||||
],
|
||||
],
|
||||
'annotations' => [
|
||||
'cache' => 'php_array',
|
||||
|
@ -29,13 +29,14 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
*/
|
||||
class NotCompromisedPasswordValidator extends ConstraintValidator
|
||||
{
|
||||
private const RANGE_API = 'https://api.pwnedpasswords.com/range/%s';
|
||||
private const DEFAULT_API_ENDPOINT = 'https://api.pwnedpasswords.com/range/%s';
|
||||
|
||||
private $httpClient;
|
||||
private $charset;
|
||||
private $disabled;
|
||||
private $enabled;
|
||||
private $endpoint;
|
||||
|
||||
public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $disabled = false)
|
||||
public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $enabled = true, string $endpoint = null)
|
||||
{
|
||||
if (null === $httpClient && !class_exists(HttpClient::class)) {
|
||||
throw new \LogicException(sprintf('The "%s" class requires the "HttpClient" component. Try running "composer require symfony/http-client".', self::class));
|
||||
@ -43,7 +44,8 @@ class NotCompromisedPasswordValidator extends ConstraintValidator
|
||||
|
||||
$this->httpClient = $httpClient ?? HttpClient::create();
|
||||
$this->charset = $charset;
|
||||
$this->disabled = $disabled;
|
||||
$this->enabled = $enabled;
|
||||
$this->endpoint = $endpoint ?? self::DEFAULT_API_ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,7 +59,7 @@ class NotCompromisedPasswordValidator extends ConstraintValidator
|
||||
throw new UnexpectedTypeException($constraint, NotCompromisedPassword::class);
|
||||
}
|
||||
|
||||
if ($this->disabled) {
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -76,7 +78,7 @@ class NotCompromisedPasswordValidator extends ConstraintValidator
|
||||
|
||||
$hash = strtoupper(sha1($value));
|
||||
$hashPrefix = substr($hash, 0, 5);
|
||||
$url = sprintf(self::RANGE_API, $hashPrefix);
|
||||
$url = sprintf($this->endpoint, $hashPrefix);
|
||||
|
||||
try {
|
||||
$result = $this->httpClient->request('GET', $url)->getContent();
|
||||
|
@ -62,9 +62,9 @@ class NotCompromisedPasswordValidatorTest extends ConstraintValidatorTestCase
|
||||
|
||||
public function testInvalidPasswordButDisabled()
|
||||
{
|
||||
$r = new \ReflectionProperty($this->validator, 'disabled');
|
||||
$r = new \ReflectionProperty($this->validator, 'enabled');
|
||||
$r->setAccessible(true);
|
||||
$r->setValue($this->validator, true);
|
||||
$r->setValue($this->validator, false);
|
||||
|
||||
$this->validator->validate(self::PASSWORD_LEAKED, new NotCompromisedPassword());
|
||||
|
||||
@ -128,6 +128,29 @@ class NotCompromisedPasswordValidatorTest extends ConstraintValidatorTestCase
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
public function testInvalidPasswordCustomEndpoint()
|
||||
{
|
||||
$endpoint = 'https://password-check.internal.example.com/range/%s';
|
||||
// 50D74 - first 5 bytes of uppercase SHA1 hash of self::PASSWORD_LEAKED
|
||||
$expectedEndpointUrl = 'https://password-check.internal.example.com/range/50D74';
|
||||
$constraint = new NotCompromisedPassword();
|
||||
|
||||
$this->context = $this->createContext();
|
||||
|
||||
$validator = new NotCompromisedPasswordValidator(
|
||||
$this->createHttpClientStubCustomEndpoint($expectedEndpointUrl),
|
||||
'UTF-8',
|
||||
true,
|
||||
$endpoint
|
||||
);
|
||||
$validator->initialize($this->context);
|
||||
$validator->validate(self::PASSWORD_LEAKED, $constraint);
|
||||
|
||||
$this->buildViolation($constraint->message)
|
||||
->setCode(NotCompromisedPassword::COMPROMISED_PASSWORD_ERROR)
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
|
||||
*/
|
||||
@ -184,4 +207,21 @@ class NotCompromisedPasswordValidatorTest extends ConstraintValidatorTestCase
|
||||
|
||||
return $httpClientStub;
|
||||
}
|
||||
|
||||
private function createHttpClientStubCustomEndpoint($expectedEndpoint): HttpClientInterface
|
||||
{
|
||||
$httpClientStub = $this->createMock(HttpClientInterface::class);
|
||||
$httpClientStub->method('request')->with('GET', $expectedEndpoint)->will(
|
||||
$this->returnCallback(function (string $method, string $url): ResponseInterface {
|
||||
$responseStub = $this->createMock(ResponseInterface::class);
|
||||
$responseStub
|
||||
->method('getContent')
|
||||
->willReturn(implode("\r\n", self::RETURN));
|
||||
|
||||
return $responseStub;
|
||||
})
|
||||
);
|
||||
|
||||
return $httpClientStub;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user