Skip to content
Permalink
Browse files

feature #31060 [Validator] Make API endpoint for NotCompromisedPasswo…

…rdValidator configurable (xelan)

This PR was squashed before being merged into the 4.3-dev branch (closes #31060).

Discussion
----------

[Validator] Make API endpoint for NotCompromisedPasswordValidator configurable

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | yes, but acceptable [1]
| Deprecations? | no [1]
| Tests pass?   | yes
| Fixed tickets | #30871, #31054
| License       | MIT
| Doc PR        | symfony/symfony-docs#... (TODO)

Makes the API endpoint for the `NotCompromisedPasswordValidator` configurable. The endpoint includes the placeholder which will be replaced with the first digits of the password hash for k-anonymity.

The endpoint can either be set via constructor injection of the validator if the component is used standalone, or via the framework configuration of symfony/framework-bundle.

[1] As discussed in #31054, the validator is not in a stable release yet, therefore the BC break is considered acceptable. No deprecation / BC layer is necessary.

Commits
-------

f6a80c2 [Validator] Make API endpoint for NotCompromisedPasswordValidator configurable
  • Loading branch information...
fabpot committed May 6, 2019
2 parents 4e61ff5 + f6a80c2 commit ecfccc6ef011b20390a9ae9a70d5c7e8e2ddf83e
@@ -834,9 +834,18 @@ private function addValidationSection(ArrayNodeDefinition $rootNode)
->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')
@@ -1262,7 +1262,8 @@ private function registerValidationConfiguration(array $config, ContainerBuilder
$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 @@ protected static function getBundleDefaultConfig()
'paths' => [],
],
'auto_mapping' => [],
'disable_not_compromised_password' => false,
'not_compromised_password' => [
'enabled' => true,
'endpoint' => null,
],
],
'annotations' => [
'cache' => 'php_array',
@@ -29,21 +29,23 @@
*/
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));
}
$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 @@ public function validate($value, Constraint $constraint)
throw new UnexpectedTypeException($constraint, NotCompromisedPassword::class);
}
if ($this->disabled) {
if (!$this->enabled) {
return;
}
@@ -76,7 +78,7 @@ public function validate($value, Constraint $constraint)
$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 @@ public function testEmptyStringIsValid()
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 @@ public function testNonUtf8CharsetInvalid()
->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 @@ public function getResponse(): ResponseInterface
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;
}
}

0 comments on commit ecfccc6

Please sign in to comment.
You can’t perform that action at this time.