From 9bec6837d8410eb164a373617806f7d2d26bd1af Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 2 Jun 2025 16:08:14 +0200 Subject: [PATCH 01/11] Allow Symfony ^8.0 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ebcb51b..1eb6681 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,8 @@ "php": ">=8.2" }, "require-dev": { - "symfony/security-core": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0" + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0" }, "conflict": { "symfony/security-core": "<6.4" From aae691a72afcef78d3062a5bb9f942c0add4d812 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 2 Jun 2025 17:50:55 +0200 Subject: [PATCH 02/11] Bump Symfony 8 to PHP >= 8.4 --- composer.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 1eb6681..6be1c06 100644 --- a/composer.json +++ b/composer.json @@ -16,14 +16,11 @@ } ], "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/security-core": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0" - }, - "conflict": { - "symfony/security-core": "<6.4" + "symfony/console": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0" }, "autoload": { "psr-4": { "Symfony\\Component\\PasswordHasher\\": "" }, From b9549e824f8e9ed2596a31d93c639ee83de3aa8e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Jun 2025 17:41:25 +0200 Subject: [PATCH 03/11] Remove deadcode after the bump to PHP >= 8.4 --- Tests/Hasher/NativePasswordHasherTest.php | 33 ----------------------- Tests/Hasher/SodiumPasswordHasherTest.php | 33 ----------------------- 2 files changed, 66 deletions(-) diff --git a/Tests/Hasher/NativePasswordHasherTest.php b/Tests/Hasher/NativePasswordHasherTest.php index a21b6d6..7ed4fbd 100644 --- a/Tests/Hasher/NativePasswordHasherTest.php +++ b/Tests/Hasher/NativePasswordHasherTest.php @@ -99,39 +99,6 @@ public function testBcryptWithLongPassword() $this->assertTrue($hasher->verify($hasher->hash($plainPassword), $plainPassword)); } - /** - * @requires PHP < 8.4 - */ - public function testBcryptWithNulByteWithNativePasswordHash() - { - $hasher = new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT); - $plainPassword = "a\0b"; - - try { - $hash = password_hash($plainPassword, \PASSWORD_BCRYPT, ['cost' => 4]); - } catch (\Throwable $throwable) { - // we skip the test in case the current PHP version does not support NUL bytes in passwords - // with bcrypt - // - // @see https://github.com/php/php-src/commit/11f2568767660ffe92fbc6799800e01203aad73a - if (str_contains($throwable->getMessage(), 'Bcrypt password must not contain null character')) { - $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); - } - - throw $throwable; - } - - if (null === $hash) { - // we also skip the test in case password_hash() returns null as - // implemented in security patches backports - // - // @see https://github.com/shivammathur/php-src-backports/commit/d22d9ebb29dce86edd622205dd1196a2796c08c7 - $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); - } - - $this->assertFalse($hasher->verify($hash, $plainPassword)); - } - public function testPasswordNulByteGracefullyHandled() { $hasher = new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT); diff --git a/Tests/Hasher/SodiumPasswordHasherTest.php b/Tests/Hasher/SodiumPasswordHasherTest.php index 0a8b7ea..aa52ce9 100644 --- a/Tests/Hasher/SodiumPasswordHasherTest.php +++ b/Tests/Hasher/SodiumPasswordHasherTest.php @@ -73,39 +73,6 @@ public function testBcryptWithLongPassword() $this->assertTrue($hasher->verify((new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT))->hash($plainPassword), $plainPassword)); } - /** - * @requires PHP < 8.4 - */ - public function testBcryptWithNulByteWithNativePasswordHash() - { - $hasher = new SodiumPasswordHasher(null, null); - $plainPassword = "a\0b"; - - try { - $hash = password_hash($plainPassword, \PASSWORD_BCRYPT, ['cost' => 4]); - } catch (\Throwable $throwable) { - // we skip the test in case the current PHP version does not support NUL bytes in passwords - // with bcrypt - // - // @see https://github.com/php/php-src/commit/11f2568767660ffe92fbc6799800e01203aad73a - if (str_contains($throwable->getMessage(), 'Bcrypt password must not contain null character')) { - $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); - } - - throw $throwable; - } - - if (null === $hash) { - // we also skip the test in case password_hash() returns null as - // implemented in security patches backports - // - // @see https://github.com/shivammathur/php-src-backports/commit/d22d9ebb29dce86edd622205dd1196a2796c08c7 - $this->markTestSkipped('password_hash() does not accept passwords containing NUL bytes.'); - } - - $this->assertFalse($hasher->verify($hash, $plainPassword)); - } - public function testPasswordNulByteGracefullyHandled() { $hasher = new SodiumPasswordHasher(null, null); From 615b48f0488b1ffe2a4d35c4de8c0a95861d4476 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Sun, 8 Jun 2025 19:33:20 +0200 Subject: [PATCH 04/11] [Security][Ldap] Remove deprecated `eraseCredentials()` from (User|Token)Interface --- Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php b/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php index ef867fa..5feb960 100644 --- a/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php +++ b/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php @@ -35,11 +35,6 @@ public function getRoles(): array return $this->roles; } - #[\Deprecated] - public function eraseCredentials(): void - { - } - public function getUserIdentifier(): string { return $this->username; From c4ecf99fe937e09bfcecd13ae98f6229851108e3 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 18 Jun 2025 12:14:41 +0200 Subject: [PATCH 05/11] fix tests when run with symfony/security-core 7.4 --- Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php b/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php index 5feb960..ef867fa 100644 --- a/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php +++ b/Tests/Fixtures/TestLegacyPasswordAuthenticatedUser.php @@ -35,6 +35,11 @@ public function getRoles(): array return $this->roles; } + #[\Deprecated] + public function eraseCredentials(): void + { + } + public function getUserIdentifier(): string { return $this->username; From 16fbde2d443546a002e50c4ec4b59a536373103c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Jul 2025 19:03:41 +0200 Subject: [PATCH 06/11] Remove legacy code paths that rely on feature checks --- Tests/Hasher/UserPasswordHasherTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/Hasher/UserPasswordHasherTest.php b/Tests/Hasher/UserPasswordHasherTest.php index 1f9c835..30008cf 100644 --- a/Tests/Hasher/UserPasswordHasherTest.php +++ b/Tests/Hasher/UserPasswordHasherTest.php @@ -19,7 +19,6 @@ use Symfony\Component\PasswordHasher\Tests\Fixtures\TestLegacyPasswordAuthenticatedUser; use Symfony\Component\PasswordHasher\Tests\Fixtures\TestPasswordAuthenticatedUser; use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\User; class UserPasswordHasherTest extends TestCase { @@ -125,7 +124,7 @@ public function testNeedsRehash() $passwordHasher = new UserPasswordHasher($mockPasswordHasherFactory); - \Closure::bind(function () use ($passwordHasher) { $this->password = $passwordHasher->hashPassword($this, 'foo', 'salt'); }, $user, class_exists(User::class) ? User::class : InMemoryUser::class)(); + \Closure::bind(function () use ($passwordHasher) { $this->password = $passwordHasher->hashPassword($this, 'foo', 'salt'); }, $user, InMemoryUser::class)(); $this->assertFalse($passwordHasher->needsRehash($user)); $this->assertTrue($passwordHasher->needsRehash($user)); $this->assertFalse($passwordHasher->needsRehash($user)); From fef029cfef22c3f6ab8acbd0d80a464b8d1ea906 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 9 Oct 2024 11:06:51 +0200 Subject: [PATCH 07/11] run tests using PHPUnit 11.5 --- phpunit.xml.dist | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f9917cc..57fdcd1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,11 @@ @@ -18,7 +19,7 @@ - + ./ @@ -27,5 +28,9 @@ ./Tests ./vendor - + + + + + From 9d30cf37cea00ba4f916a3e6041639ae18387932 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 31 Jul 2025 14:36:46 +0200 Subject: [PATCH 08/11] replace PHPUnit annotations with attributes --- Tests/Command/UserPasswordHashCommandTest.php | 5 ++--- Tests/Hasher/NativePasswordHasherTest.php | 17 +++++++---------- Tests/Hasher/SodiumPasswordHasherTest.php | 5 ++--- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Tests/Command/UserPasswordHashCommandTest.php b/Tests/Command/UserPasswordHashCommandTest.php index 819a928..2ed9893 100644 --- a/Tests/Command/UserPasswordHashCommandTest.php +++ b/Tests/Command/UserPasswordHashCommandTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\PasswordHasher\Tests\Command; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; @@ -287,9 +288,7 @@ public function testThrowsExceptionOnNoConfiguredHashers() ], ['interactive' => false]); } - /** - * @dataProvider provideCompletionSuggestions - */ + #[DataProvider('provideCompletionSuggestions')] public function testCompletionSuggestions(array $input, array $expectedSuggestions) { $command = new UserPasswordHashCommand($this->createMock(PasswordHasherFactoryInterface::class), ['App\Entity\User']); diff --git a/Tests/Hasher/NativePasswordHasherTest.php b/Tests/Hasher/NativePasswordHasherTest.php index a21b6d6..da86e6b 100644 --- a/Tests/Hasher/NativePasswordHasherTest.php +++ b/Tests/Hasher/NativePasswordHasherTest.php @@ -11,6 +11,9 @@ namespace Symfony\Component\PasswordHasher\Tests\Hasher; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; @@ -32,9 +35,7 @@ public function testCostAboveRange() new NativePasswordHasher(null, null, 32); } - /** - * @dataProvider validRangeData - */ + #[DataProvider('validRangeData')] public function testCostInRange($cost) { $this->assertInstanceOf(NativePasswordHasher::class, new NativePasswordHasher(null, null, $cost)); @@ -99,9 +100,7 @@ public function testBcryptWithLongPassword() $this->assertTrue($hasher->verify($hasher->hash($plainPassword), $plainPassword)); } - /** - * @requires PHP < 8.4 - */ + #[RequiresPhp('<8.4')] public function testBcryptWithNulByteWithNativePasswordHash() { $hasher = new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT); @@ -169,10 +168,8 @@ public function testLowMemLimitThrows() new NativePasswordHasher(3, 9999); } - /** - * @testWith [1] - * [40] - */ + #[TestWith([1])] + #[TestWith([40])] public function testInvalidCostThrows(int $cost) { $this->expectException(\InvalidArgumentException::class); diff --git a/Tests/Hasher/SodiumPasswordHasherTest.php b/Tests/Hasher/SodiumPasswordHasherTest.php index 0a8b7ea..fd0a42d 100644 --- a/Tests/Hasher/SodiumPasswordHasherTest.php +++ b/Tests/Hasher/SodiumPasswordHasherTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\PasswordHasher\Tests\Hasher; +use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\TestCase; use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; @@ -73,9 +74,7 @@ public function testBcryptWithLongPassword() $this->assertTrue($hasher->verify((new NativePasswordHasher(null, null, 4, \PASSWORD_BCRYPT))->hash($plainPassword), $plainPassword)); } - /** - * @requires PHP < 8.4 - */ + #[RequiresPhp('<8.4')] public function testBcryptWithNulByteWithNativePasswordHash() { $hasher = new SodiumPasswordHasher(null, null); From 43747c560959fb7b6648d494741fa2c958c9e213 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 24 Jun 2025 13:39:09 +0100 Subject: [PATCH 09/11] Remove some implicit bool type juggling --- Hasher/SodiumPasswordHasher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hasher/SodiumPasswordHasher.php b/Hasher/SodiumPasswordHasher.php index ae6c03f..506cb0e 100644 --- a/Hasher/SodiumPasswordHasher.php +++ b/Hasher/SodiumPasswordHasher.php @@ -49,7 +49,7 @@ public function __construct(?int $opsLimit = null, ?int $memLimit = null) public static function isSupported(): bool { - return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : phpversion('libsodium'), '1.0.14', '>='); + return version_compare(\extension_loaded('sodium') ? \SODIUM_LIBRARY_VERSION : (phpversion('libsodium') ?: ''), '1.0.14', '>='); } public function hash(#[\SensitiveParameter] string $plainPassword): string From 2ca0a154bbbc631b98f4c054b7784b65e2e1ae84 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 10 Aug 2025 00:28:14 +0200 Subject: [PATCH 10/11] chore: heredoc indentation as of PHP 7.3 https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc --- Command/UserPasswordHashCommand.php | 50 +++++++++---------- Tests/Command/UserPasswordHashCommandTest.php | 12 ++--- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Command/UserPasswordHashCommand.php b/Command/UserPasswordHashCommand.php index 574ad3f..6eeb933 100644 --- a/Command/UserPasswordHashCommand.php +++ b/Command/UserPasswordHashCommand.php @@ -53,42 +53,42 @@ protected function configure(): void ->addOption('empty-salt', null, InputOption::VALUE_NONE, 'Do not generate a salt or let the hasher generate one.') ->setHelp(<<%command.name% command hashes passwords according to your -security configuration. This command is mainly used to generate passwords for -the in_memory user provider type and for changing passwords -in the database while developing the application. + The %command.name% command hashes passwords according to your + security configuration. This command is mainly used to generate passwords for + the in_memory user provider type and for changing passwords + in the database while developing the application. -Suppose that you have the following security configuration in your application: + Suppose that you have the following security configuration in your application: - -# config/packages/security.yml -security: - password_hashers: - Symfony\Component\Security\Core\User\InMemoryUser: plaintext - App\Entity\User: auto - + + # config/packages/security.yml + security: + password_hashers: + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + App\Entity\User: auto + -If you execute the command non-interactively, the first available configured -user class under the security.password_hashers key is used and a random salt is -generated to hash the password: + If you execute the command non-interactively, the first available configured + user class under the security.password_hashers key is used and a random salt is + generated to hash the password: - php %command.full_name% --no-interaction [password] + php %command.full_name% --no-interaction [password] -Pass the full user class path as the second argument to hash passwords for -your own entities: + Pass the full user class path as the second argument to hash passwords for + your own entities: - php %command.full_name% --no-interaction [password] 'App\Entity\User' + php %command.full_name% --no-interaction [password] 'App\Entity\User' -Executing the command interactively allows you to generate a random salt for -hashing the password: + Executing the command interactively allows you to generate a random salt for + hashing the password: - php %command.full_name% [password] 'App\Entity\User' + php %command.full_name% [password] 'App\Entity\User' -In case your hasher doesn't require a salt, add the empty-salt option: + In case your hasher doesn't require a salt, add the empty-salt option: - php %command.full_name% --empty-salt [password] 'App\Entity\User' + php %command.full_name% --empty-salt [password] 'App\Entity\User' -EOF + EOF ) ; } diff --git a/Tests/Command/UserPasswordHashCommandTest.php b/Tests/Command/UserPasswordHashCommandTest.php index 2ed9893..7366b07 100644 --- a/Tests/Command/UserPasswordHashCommandTest.php +++ b/Tests/Command/UserPasswordHashCommandTest.php @@ -258,12 +258,12 @@ public function testEncodePasswordAsksNonProvidedUserClass() ], ['decorated' => false]); $this->assertStringContainsString(<<passwordHasherCommandTester->getDisplay(true)); } From aa075ce6f54fe931f03c1e382597912f4fd94e1e Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 10 Aug 2025 00:12:49 +0200 Subject: [PATCH 11/11] chore: PHP CS Fixer - update heredoc handling --- Tests/Command/UserPasswordHashCommandTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/Command/UserPasswordHashCommandTest.php b/Tests/Command/UserPasswordHashCommandTest.php index 7366b07..d2509ca 100644 --- a/Tests/Command/UserPasswordHashCommandTest.php +++ b/Tests/Command/UserPasswordHashCommandTest.php @@ -263,8 +263,9 @@ public function testEncodePasswordAsksNonProvidedUserClass() [1] Custom\Class\Pbkdf2\User [2] Custom\Class\Test\User [3] Symfony\Component\Security\Core\User\InMemoryUser - EOTXT - , $this->passwordHasherCommandTester->getDisplay(true)); + EOTXT, + $this->passwordHasherCommandTester->getDisplay(true) + ); } public function testNonInteractiveEncodePasswordUsesFirstUserClass()