10000 Add a normalization step for the user-identifier in firewalls · symfony/symfony@adb4098 · GitHub
[go: up one dir, main page]

Skip to content

Commit adb4098

Browse files
committed
Add a normalization step for the user-identifier in firewalls
1 parent 49d00f6 commit adb4098

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class UserBadge implements BadgeInterface
3636
private UserInterface $user;
3737
private ?array $attributes;
3838

39+
private ?\Closure $identifierNormalizer = null;
40+
3941
/**
4042
* Initializes the user badge.
4143
*
@@ -49,19 +51,30 @@ class UserBadge implements BadgeInterface
4951
* is thrown). If this is not set, the default user provider will be used with
5052
* $userIdentifier as username.
5153
*/
52-
public function __construct(string $userIdentifier, callable $userLoader = null, array $attributes = null)
54+
public function __construct(string $userIdentifier, callable $userLoader = null, array $attributes = null, ?\Closure $identifierNormalizer = null)
5355
{
5456
if (\strlen($userIdentifier) > self::MAX_USERNAME_LENGTH) {
5557
throw new BadCredentialsException('Username too long.');
5658
}
59+
if ($identifierNormalizer) {
60+
$this->identifierNormalizer = static fn () => $identifierNormalizer($userIdentifier);
61+
} else {
62+
$this->userIdentifier = $userIdentifier;
63+
}
5764

58-
$this->userIdentifier = $userIdentifier;
5965
$this->userLoader = $userLoader;
6066
$this->attributes = $attributes;
6167
}
6268

6369
public function getUserIdentifier(): string
6470
{
71+
if (isset($this->userIdentifier)) {
72+
return $this->userIdentifier;
73+
}
74+
75+
$this->userIdentifier = ($this->identifierNormalizer)();
76+
$this->identifierNormalizer = null;
77+
6578
return $this->userIdentifier;
6679
}
6780

@@ -84,15 +97,15 @@ public function getUser(): UserInterface
8497
}
8598

8699
if (null === $this->getAttributes()) {
87-
$user = ($this->userLoader)($this->userIdentifier);
100+
$user = ($this->userLoader)($this->getUserIdentifier());
88101
} else {
89-
$user = ($this->userLoader)($this->userIdentifier, $this->getAttributes());
102+
$user = ($this->userLoader)($this->getUserIdentifier(), $this->getAttributes());
90103
}
91104

92105
// No user has been found via the $this->userLoader callback
93106
if (null === $user) {
94107
$exception = new UserNotFoundException();
95-
$exception->setUserIdentifier($this->userIdentifier);
108+
$exception->setUserIdentifier($this->getUserIdentifier());
96109

97110
throw $exception;
98111
}

src/Symfony/Component/Security/Http/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add `#[IsCsrfTokenValid]` attribute
8+
* Add argument `$identifierNormalizer` to `UserBadge::__construct()` to allow normalizing the identifier
89

910
7.0
1011
---

src/Symfony/Component/Security/Http/Tests/Authenticator/Passport/Badge/UserBadgeTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
1616
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
17+
use Symfony\Component\String\Slugger\AsciiSlugger;
18+
use Symfony\Component\String\UnicodeString;
19+
20+
use function Symfony\Component\String\u;
1721

1822
class UserBadgeTest extends TestCase
1923
{
@@ -23,4 +27,29 @@ public function testUserNotFound()
2327
$this->expectException(UserNotFoundException::class);
2428
$badge->getUser();
2529
}
30+
31+
/**
32+
* @dataProvider provideUserIdentifierNormalizationData
33+
*/
34+
public function testUserIdentifierNormalization(string $identifier, string $expectedNormalizedIdentifier, callable $normalizer)
35+
{
36+
$badge = new UserBadge($identifier, fn () => null, identifierNormalizer: $normalizer);
37+
38+
static::assertSame($expectedNormalizedIdentifier, $badge->getUserIdentifier());
39+
}
40+
41+
public static function provideUserIdentifierNormalizationData(): iterable
42+
{
43+
$lowerAndNFKC = fn (string $identifier) => u($identifier)->normalize(UnicodeString::NFKC)->lower()->toString();
44+
$upperAndAscii = fn (string $identifier) => u($identifier)->ascii()->upper()->toString();
45+
$slugger = new AsciiSlugger('en');
46+
$asciiWithPrefix = fn (string $identifier) => u($slugger->slug($identifier))->ascii()->lower()->prepend('USERID--')->toString();
47+
48+
yield 'Simple lower conversion' => ['SmiTh', 'smith', $lowerAndNFKC];
49+
yield 'Normalize fi to fi. Other unicode characters are preserved (р, с, ѕ and а)' => ['рrinсeѕѕ.fionа', 'рrinсeѕѕ.fionа', $lowerAndNFKC];
50+
yield 'Greek characters' => ['ΝιΚόΛΑος', 'νικόλαος', $lowerAndNFKC];
51+
yield 'Greek to ASCII' => ['ΝιΚόΛΑος', 'NIKOLAOS', $upperAndAscii];
52+
yield 'Katakana to ASCII' => ['たなかそういち', 'TANAKASOUICHI', $upperAndAscii];
53+
yield 'Username with prefix' => ['John Doe 1', 'USERID--john-doe-1', $asciiWithPrefix];
54+
}
2655
}

0 commit comments

Comments
 (0)
0