Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ecfccc6

Browse files
committed
feature #31060 [Validator] Make API endpoint for NotCompromisedPasswordValidator 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
2 parents 4e61ff5 + f6a80c2 commit ecfccc6

File tree

5 files changed

+68
-13
lines changed

5 files changed

+68
-13
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,18 @@ private function addValidationSection(ArrayNodeDefinition $rootNode)
834834
->end()
835835
->end()
836836
->end()
837-
->booleanNode('disable_not_compromised_password')
838-
->defaultFalse()
839-
->info('Disable NotCompromisedPassword Validator: the value will always be valid.')
837+
->arrayNode('not_compromised_password')
838+
->canBeDisabled()
839+
->children()
840+
->booleanNode('enabled')
841+
->defaultTrue()
842+
->info('When disabled, compromised passwords will be accepted as valid.')
843+
->end()
844+
->scalarNode('endpoint')
845+
->defaultNull()
846+
->info('API endpoint for the NotCompromisedPassword Validator.')
847+
->end()
848+
->end()
840849
->end()
841850
->arrayNode('auto_mapping')
842851
->useAttributeAsKey('namespace')

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,8 @@ private function registerValidationConfiguration(array $config, ContainerBuilder
12621262

12631263
$container
12641264
->getDefinition('validator.not_compromised_password')
1265-
->setArgument(2, $config['disable_not_compromised_password'])
1265+
->setArgument(2, $config['not_compromised_password']['enabled'])
1266+
->setArgument(3, $config['not_compromised_password']['endpoint'])
12661267
;
12671268
}
12681269

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,10 @@ protected static function getBundleDefaultConfig()
235235
'paths' => [],
236236
],
237237
'auto_mapping' => [],
238-
'disable_not_compromised_password' => false,
238+
'not_compromised_password' => [
239+
'enabled' => true,
240+
'endpoint' => null,
241+
],
239242
],
240243
'annotations' => [
241244
'cache' => 'php_array',

src/Symfony/Component/Validator/Constraints/NotCompromisedPasswordValidator.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,23 @@
2929
*/
3030
class NotCompromisedPasswordValidator extends ConstraintValidator
3131
{
32-
private const RANGE_API = 'https://api.pwnedpasswords.com/range/%s';
32+
private const DEFAULT_API_ENDPOINT = 'https://api.pwnedpasswords.com/range/%s';
3333

3434
private $httpClient;
3535
private $charset;
36-
private $disabled;
36+
private $enabled;
37+
private $endpoint;
3738

38-
public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $disabled = false)
39+
public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8', bool $enabled = true, string $endpoint = null)
3940
{
4041
if (null === $httpClient && !class_exists(HttpClient::class)) {
4142
throw new \LogicException(sprintf('The "%s" class requires the "HttpClient" component. Try running "composer require symfony/http-client".', self::class));
4243
}
4344

4445
$this->httpClient = $httpClient ?? HttpClient::create();
4546
$this->charset = $charset;
46-
$this->disabled = $disabled;
47+
$this->enabled = $enabled;
48+
$this->endpoint = $endpoint ?? self::DEFAULT_API_ENDPOINT;
4749
}
4850

4951
/**
@@ -57,7 +59,7 @@ public function validate($value, Constraint $constraint)
5759
throw new UnexpectedTypeException($constraint, NotCompromisedPassword::class);
5860
}
5961

60-
if ($this->disabled) {
62+
if (!$this->enabled) {
6163
return;
6264
}
6365

@@ -76,7 +78,7 @@ public function validate($value, Constraint $constraint)
7678

7779
$hash = strtoupper(sha1($value));
7880
$hashPrefix = substr($hash, 0, 5);
79-
$url = sprintf(self::RANGE_API, $hashPrefix);
81+
$url = sprintf($this->endpoint, $hashPrefix);
8082

8183
try {
8284
$result = $this->httpClient->request('GET', $url)->getContent();

src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ public function testEmptyStringIsValid()
6262

6363
public function testInvalidPasswordButDisabled()
6464
{
65-
$r = new \ReflectionProperty($this->validator, 'disabled');
65+
$r = new \ReflectionProperty($this->validator, 'enabled');
6666
$r->setAccessible(true);
67-
$r->setValue($this->validator, true);
67+
$r->setValue($this->validator, false);
6868

6969
$this->validator->validate(self::PASSWORD_LEAKED, new NotCompromisedPassword());
7070

@@ -128,6 +128,29 @@ public function testNonUtf8CharsetInvalid()
128128
->assertRaised();
129129
}
130130

131+
public function testInvalidPasswordCustomEndpoint()
132+
{
133+
$endpoint = 'https://password-check.internal.example.com/range/%s';
134+
// 50D74 - first 5 bytes of uppercase SHA1 hash of self::PASSWORD_LEAKED
135+
$expectedEndpointUrl = 'https://password-check.internal.example.com/range/50D74';
136+
$constraint = new NotCompromisedPassword();
137+
138+
$this->context = $this->createContext();
139+
140+
$validator = new NotCompromisedPasswordValidator(
141+
$this->createHttpClientStubCustomEndpoint($expectedEndpointUrl),
142+
'UTF-8',
143+
true,
144+
$endpoint
145+
);
146+
$validator->initialize($this->context);
147+
$validator->validate(self::PASSWORD_LEAKED, $constraint);
148+
149+
$this->buildViolation($constraint->message)
150+
->setCode(NotCompromisedPassword::COMPROMISED_PASSWORD_ERROR)
151+
->assertRaised();
152+
}
153+
131154
/**
132155
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
133156
*/
@@ -184,4 +207,21 @@ public function getResponse(): ResponseInterface
184207

185208
return $httpClientStub;
186209
}
210+
211+
private function createHttpClientStubCustomEndpoint($expectedEndpoint): HttpClientInterface
212+
{
213+
$httpClientStub = $this->createMock(HttpClientInterface::class);
214+
$httpClientStub->method('request')->with('GET', $expectedEndpoint)->will(
215+
$this->returnCallback(function (string $method, string $url): ResponseInterface {
216+
$responseStub = $this->createMock(ResponseInterface::class);
217+
$responseStub
218+
->method('getContent')
219+
->willReturn(implode("\r\n", self::RETURN));
220+
221+
return $responseStub;
222+
})
223+
);
224+
225+
return $httpClientStub;
226+
}
187227
}

0 commit comments

Comments
 (0)