diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 86cef124a568..d2649c28718d 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -32,6 +32,7 @@ use Illuminate\Support\Exceptions\MathException; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Date; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; use InvalidArgumentException; use LogicException; @@ -104,6 +105,7 @@ trait HasAttributes 'encrypted:json', 'encrypted:object', 'float', + 'hashed', 'immutable_date', 'immutable_datetime', 'immutable_custom_datetime', @@ -985,6 +987,10 @@ public function setAttribute($key, $value) $value = $this->castAttributeAsEncryptedString($key, $value); } + if (! is_null($value) && $this->hasCast($key, 'hashed')) { + $value = $this->castAttributeAsHashedString($key, $value); + } + $this->attributes[$key] = $value; return $this; @@ -1293,6 +1299,18 @@ public static function encryptUsing($encrypter) static::$encrypter = $encrypter; } + /** + * Cast the given attribute to a hashed string. + * + * @param string $key + * @param mixed $value + * @return string + */ + protected function castAttributeAsHashedString($key, $value) + { + return Hash::needsRehash($value) ? Hash::make($value) : $value; + } + /** * Decode the given float. * diff --git a/tests/Integration/Database/EloquentModelHashedCastingTest.php b/tests/Integration/Database/EloquentModelHashedCastingTest.php new file mode 100644 index 000000000000..85b700c07f59 --- /dev/null +++ b/tests/Integration/Database/EloquentModelHashedCastingTest.php @@ -0,0 +1,91 @@ +hasher = $this->mock(Hasher::class); + Hash::swap($this->hasher); + } + + protected function defineDatabaseMigrationsAfterDatabaseRefreshed() + { + Schema::create('hashed_casts', function (Blueprint $table) { + $table->increments('id'); + $table->string('password')->nullable(); + }); + } + + public function testHashed() + { + $this->hasher->expects('needsRehash') + ->with('this is a password') + ->andReturnTrue(); + + $this->hasher->expects('make') + ->with('this is a password') + ->andReturn('hashed-password'); + + $subject = HashedCast::create([ + 'password' => 'this is a password', + ]); + + $this->assertSame('hashed-password', $subject->password); + $this->assertDatabaseHas('hashed_casts', [ + 'id' => $subject->id, + 'password' => 'hashed-password', + ]); + } + + public function testNotHashedIfAlreadyHashed() + { + $this->hasher->expects('needsRehash') + ->with('already-hashed-password') + ->andReturnFalse(); + + $subject = HashedCast::create([ + 'password' => 'already-hashed-password', + ]); + + $this->assertSame('already-hashed-password', $subject->password); + $this->assertDatabaseHas('hashed_casts', [ + 'id' => $subject->id, + 'password' => 'already-hashed-password', + ]); + } + + public function testNotHashedIfNull() + { + $subject = HashedCast::create([ + 'password' => null, + ]); + + $this->assertNull($subject->password); + $this->assertDatabaseHas('hashed_casts', [ + 'id' => $subject->id, + 'password' => null, + ]); + } +} + +class HashedCast extends Model +{ + public $timestamps = false; + protected $guarded = []; + + public $casts = [ + 'password' => 'hashed', + ]; +}