Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b045aca

Browse filesBrowse files
committed
feature #30898 [Validator] Wire NotCompromisedPassword in FrameworkBundle and handle non UTF-8 password (tgalopin)
This PR was squashed before being merged into the 4.3-dev branch (closes #30898). Discussion ---------- [Validator] Wire NotCompromisedPassword in FrameworkBundle and handle non UTF-8 password | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #30870 | License | MIT | Doc PR | - Live from #eu-fossa Fix #30870 Commits ------- 8ac712b [Validator] Wire NotCompromisedPassword in FrameworkBundle and handle non UTF-8 password
2 parents ede6660 + 8ac712b commit b045aca
Copy full SHA for b045aca

File tree

Expand file treeCollapse file tree

3 files changed

+68
-25
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+68
-25
lines changed

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@
6161
<tag name="validator.constraint_validator" alias="Symfony\Component\Validator\Constraints\EmailValidator" />
6262
</service>
6363

64+
<service id="validator.not_compromised_password" class="Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator">
65+
<argument type="service" id="http_client" on-invalid="null" />
66+
<argument>%kernel.charset%</argument>
67+
<tag name="validator.constraint_validator" alias="Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator" />
68+
</service>
69+
6470
<service id="validator.property_info_loader" class="Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader">
6571
<argument type="service" id="property_info" />
6672
<argument type="service" id="property_info" />

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/NotCompromisedPasswordValidator.php
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ class NotCompromisedPasswordValidator extends ConstraintValidator
3131
private const RANGE_API = 'https://api.pwnedpasswords.com/range/%s';
3232

3333
private $httpClient;
34+
private $charset;
3435

35-
public function __construct(HttpClientInterface $httpClient = null)
36+
public function __construct(HttpClientInterface $httpClient = null, string $charset = 'UTF-8')
3637
{
3738
if (null === $httpClient && !class_exists(HttpClient::class)) {
3839
throw new \LogicException(sprintf('The "%s" class requires the "HttpClient" component. Try running "composer require symfony/http-client".', self::class));
3940
}
4041

4142
$this->httpClient = $httpClient ?? HttpClient::create();
43+
$this->charset = $charset;
4244
}
4345

4446
/**
@@ -61,6 +63,10 @@ public function validate($value, Constraint $constraint)
6163
return;
6264
}
6365

66+
if ('UTF-8' !== $this->charset) {
67+
$value = mb_convert_encoding($value, 'UTF-8', $this->charset);
68+
}
69+
6470
$hash = strtoupper(sha1($value));
6571
$hashPrefix = substr($hash, 0, 5);
6672
$url = sprintf(self::RANGE_API, $hashPrefix);

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Tests/Constraints/NotCompromisedPasswordValidatorTest.php
+55-24Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,40 +28,22 @@ class NotCompromisedPasswordValidatorTest extends ConstraintValidatorTestCase
2828
private const PASSWORD_TRIGGERING_AN_ERROR_RANGE_URL = 'https://api.pwnedpasswords.com/range/3EF27'; // https://api.pwnedpasswords.com/range/3EF27 is the range for the value "apiError"
2929
private const PASSWORD_LEAKED = 'maman';
3030
private const PASSWORD_NOT_LEAKED = ']<0585"%sb^5aa$w6!b38",,72?dp3r4\45b28Hy';
31+
private const PASSWORD_NON_UTF8_LEAKED = 'мама';
32+
private const PASSWORD_NON_UTF8_NOT_LEAKED = 'м<в0dp3r4\45b28Hy';
3133

3234
private const RETURN = [
3335
'35E033023A46402F94CFB4F654C5BFE44A1:1',
3436
'35F079CECCC31812288257CD770AA7968D7:53',
35-
'36039744C253F9B2A4E90CBEDB02EBFB82D:5', // this is the matching line, password: maman
37+
'36039744C253F9B2A4E90CBEDB02EBFB82D:5', // UTF-8 leaked password: maman
38+
'273CA8A2A78C9B2D724144F4FAF4D221C86:6', // ISO-8859-5 leaked password: мама
3639
'3686792BBC66A72D40D928ED15621124CFE:7',
3740
'36EEC709091B810AA240179A44317ED415C:2',
3841
];
3942

4043
protected function createValidator()
4144
{
42-
$httpClientStub = $this->createMock(HttpClientInterface::class);
43-
$httpClientStub->method('request')->will(
44-
$this->returnCallback(function (string $method, string $url): ResponseInterface {
45-
if (self::PASSWORD_TRIGGERING_AN_ERROR_RANGE_URL === $url) {
46-
throw new class('Problem contacting the Have I been Pwned API.') extends \Exception implements ServerExceptionInterface {
47-
public function getResponse(): ResponseInterface
48-
{
49-
throw new \RuntimeException('Not implemented');
50-
}
51-
};
52-
}
53-
54-
$responseStub = $this->createMock(ResponseInterface::class);
55-
$responseStub
56-
->method('getContent')
57-
->willReturn(implode("\r\n", self::RETURN));
58-
59-
return $responseStub;
60-
})
61-
);
62-
63-
// Pass HttpClient::create() instead of this mock to run the tests against the real API
64-
return new NotCompromisedPasswordValidator($httpClientStub);
45+
// Pass HttpClient::create() instead of the mock to run the tests against the real API
46+
return new NotCompromisedPasswordValidator($this->createHttpClientStub());
6547
}
6648

6749
public function testNullIsValid()
@@ -112,6 +94,29 @@ public function testValidPassword()
11294
$this->assertNoViolation();
11395
}
11496

97+
public function testNonUtf8CharsetValid()
98+
{
99+
$validator = new NotCompromisedPasswordValidator($this->createHttpClientStub(), 'ISO-8859-5');
100+
$validator->validate(mb_convert_encoding(self::PASSWORD_NON_UTF8_NOT_LEAKED, 'ISO-8859-5', 'UTF-8'), new NotCompromisedPassword());
101+
102+
$this->assertNoViolation();
103+
}
104+
105+
public function testNonUtf8CharsetInvalid()
106+
{
107+
$constraint = new NotCompromisedPassword();
108+
109+
$this->context = $this->createContext();
110+
111+
$validator = new NotCompromisedPasswordValidator($this->createHttpClientStub(), 'ISO-8859-5');
112+
$validator->initialize($this->context);
113+
$validator->validate(mb_convert_encoding(self::PASSWORD_NON_UTF8_LEAKED, 'ISO-8859-5', 'UTF-8'), $constraint);
114+
115+
$this->buildViolation($constraint->message)
116+
->setCode(NotCompromisedPassword::COMPROMISED_PASSWORD_ERROR)
117+
->assertRaised();
118+
}
119+
115120
/**
116121
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
117122
*/
@@ -142,4 +147,30 @@ public function testApiErrorSkipped()
142147
$this->validator->validate(self::PASSWORD_TRIGGERING_AN_ERROR, new NotCompromisedPassword(['skipOnError' => true]));
143148
$this->assertTrue(true); // No exception have been thrown
144149
}
150+
151+
private function createHttpClientStub(): HttpClientInterface
152+
{
153+
$httpClientStub = $this->createMock(HttpClientInterface::class);
154+
$httpClientStub->method('request')->will(
155+
$this->returnCallback(function (string $method, string $url): ResponseInterface {
156+
if (self::PASSWORD_TRIGGERING_AN_ERROR_RANGE_URL === $url) {
157+
throw new class('Problem contacting the Have I been Pwned API.') extends \Exception implements ServerExceptionInterface {
158+
public function getResponse(): ResponseInterface
159+
{
160+
throw new \RuntimeException('Not implemented');
161+
}
162+
};
163+
}
164+
165+
$responseStub = $this->createMock(ResponseInterface::class);
166+
$responseStub
167+
->method('getContent')
168+
->willReturn(implode("\r\n", self::RETURN));
169+
170+
return $responseStub;
171+
})
172+
);
173+
174+
return $httpClientStub;
175+
}
145176
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.