diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 309e83c66079..fbbe37ac74fb 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1310,7 +1310,7 @@ public static function encryptUsing($encrypter) */ protected function castAttributeAsHashedString($key, $value) { - return $value !== null && password_get_info($value)['algo'] === null ? Hash::make($value) : $value; + return $value !== null && ! Hash::isHashed($value) ? Hash::make($value) : $value; } /** diff --git a/src/Illuminate/Hashing/HashManager.php b/src/Illuminate/Hashing/HashManager.php index 5584f7a1d026..564411e4df54 100644 --- a/src/Illuminate/Hashing/HashManager.php +++ b/src/Illuminate/Hashing/HashManager.php @@ -88,6 +88,17 @@ public function needsRehash($hashedValue, array $options = []) return $this->driver()->needsRehash($hashedValue, $options); } + /** + * Determine if a given string is already hashed. + * + * @param string $value + * @return bool + */ + public function isHashed($value) + { + return password_get_info($value)['algo'] !== null; + } + /** * Get the default driver name. * diff --git a/tests/Hashing/HasherTest.php b/tests/Hashing/HasherTest.php index 9c70666135f0..6b1bef6f0c15 100755 --- a/tests/Hashing/HasherTest.php +++ b/tests/Hashing/HasherTest.php @@ -2,14 +2,29 @@ namespace Illuminate\Tests\Hashing; +use Illuminate\Config\Repository as Config; +use Illuminate\Container\Container; use Illuminate\Hashing\Argon2IdHasher; use Illuminate\Hashing\ArgonHasher; use Illuminate\Hashing\BcryptHasher; +use Illuminate\Hashing\HashManager; use PHPUnit\Framework\TestCase; use RuntimeException; class HasherTest extends TestCase { + public $hashManager; + + public function setUp(): void + { + parent::setUp(); + + $container = Container::setInstance(new Container); + $container->singleton('config', fn () => new Config()); + + $this->hashManager = new HashManager($container); + } + public function testEmptyHashedValueReturnsFalse() { $hasher = new BcryptHasher(); @@ -39,6 +54,7 @@ public function testBasicBcryptHashing() $this->assertFalse($hasher->needsRehash($value)); $this->assertTrue($hasher->needsRehash($value, ['rounds' => 1])); $this->assertSame('bcrypt', password_get_info($value)['algoName']); + $this->assertTrue($this->hashManager->isHashed($value)); } public function testBasicArgon2iHashing() @@ -50,6 +66,7 @@ public function testBasicArgon2iHashing() $this->assertFalse($hasher->needsRehash($value)); $this->assertTrue($hasher->needsRehash($value, ['threads' => 1])); $this->assertSame('argon2i', password_get_info($value)['algoName']); + $this->assertTrue($this->hashManager->isHashed($value)); } public function testBasicArgon2idHashing() @@ -61,6 +78,7 @@ public function testBasicArgon2idHashing() $this->assertFalse($hasher->needsRehash($value)); $this->assertTrue($hasher->needsRehash($value, ['threads' => 1])); $this->assertSame('argon2id', password_get_info($value)['algoName']); + $this->assertTrue($this->hashManager->isHashed($value)); } /** @@ -98,4 +116,9 @@ public function testBasicArgon2idVerification() $bcryptHashed = $bcryptHasher->make('password'); (new Argon2IdHasher(['verify' => true]))->check('password', $bcryptHashed); } + + public function testIsHashedWithNonHashedValue() + { + $this->assertFalse($this->hashManager->isHashed('foo')); + } } diff --git a/tests/Integration/Database/EloquentModelHashedCastingTest.php b/tests/Integration/Database/EloquentModelHashedCastingTest.php index 5d5025752f90..df3402f8e46f 100644 --- a/tests/Integration/Database/EloquentModelHashedCastingTest.php +++ b/tests/Integration/Database/EloquentModelHashedCastingTest.php @@ -30,6 +30,10 @@ protected function defineDatabaseMigrationsAfterDatabaseRefreshed() public function testHashed() { + $this->hasher->expects('isHashed') + ->with('this is a password') + ->andReturnFalse(); + $this->hasher->expects('make') ->with('this is a password') ->andReturn('hashed-password'); @@ -47,14 +51,18 @@ public function testHashed() public function testNotHashedIfAlreadyHashed() { + $this->hasher->expects('isHashed') + ->with('already-hashed-password') + ->andReturnTrue(); + $subject = HashedCast::create([ - 'password' => $hashedPassword = '$argon2i$v=19$m=65536,t=4,p=1$RHFPR1Zjc1p5cUVXTVJEcg$ooJoZb7NOa3r35WeeDRvnFwBTfaqlbbo1WcdJP5nPp8', + 'password' => 'already-hashed-password', ]); - $this->assertSame($hashedPassword, $subject->password); + $this->assertSame('already-hashed-password', $subject->password); $this->assertDatabaseHas('hashed_casts', [ 'id' => $subject->id, - 'password' => $hashedPassword, + 'password' => 'already-hashed-password', ]); }