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/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 diff --git a/Tests/Command/UserPasswordHashCommandTest.php b/Tests/Command/UserPasswordHashCommandTest.php index 819a928..d2509ca 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; @@ -257,13 +258,14 @@ public function testEncodePasswordAsksNonProvidedUserClass() ], ['decorated' => false]); $this->assertStringContainsString(<<passwordHasherCommandTester->getDisplay(true)); + For which user class would you like to hash a password? [Custom\Class\Native\User]: + [0] Custom\Class\Native\User + [1] Custom\Class\Pbkdf2\User + [2] Custom\Class\Test\User + [3] Symfony\Component\Security\Core\User\InMemoryUser + EOTXT, + $this->passwordHasherCommandTester->getDisplay(true) + ); } public function testNonInteractiveEncodePasswordUsesFirstUserClass() @@ -287,9 +289,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..3fab4d7 100644 --- a/Tests/Hasher/NativePasswordHasherTest.php +++ b/Tests/Hasher/NativePasswordHasherTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\PasswordHasher\Tests\Hasher; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWithJson; use PHPUnit\Framework\TestCase; use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; @@ -32,9 +34,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,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); @@ -169,10 +136,8 @@ public function testLowMemLimitThrows() new NativePasswordHasher(3, 9999); } - /** - * @testWith [1] - * [40] - */ + #[TestWithJson('[1]')] + #[TestWithJson('[40]')] public function testInvalidCostThrows(int $cost) { $this->expectException(\InvalidArgumentException::class); 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); 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)); diff --git a/composer.json b/composer.json index ebcb51b..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", - "symfony/console": "^6.4|^7.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\\": "" }, 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 - + + + + +