diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 924f00bb565a..10187f50d701 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1328,7 +1328,7 @@ public function fromJson($value, $asObject = false) */ public function fromEncryptedString($value) { - return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false); + return static::currentEncrypter()->decrypt($value, false); } /** @@ -1340,7 +1340,7 @@ public function fromEncryptedString($value) */ protected function castAttributeAsEncryptedString($key, $value) { - return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false); + return static::currentEncrypter()->encrypt($value, false); } /** @@ -1354,6 +1354,16 @@ public static function encryptUsing($encrypter) static::$encrypter = $encrypter; } + /** + * Get the current encrypter being used by the model. + * + * @return \Illuminate\Contracts\Encryption\Encrypter + */ + protected static function currentEncrypter() + { + return static::$encrypter ?? Crypt::getFacadeRoot(); + } + /** * Cast the given attribute to a hashed string. * @@ -2145,7 +2155,7 @@ public function originalIsEquivalent($key) } return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4; - } elseif ($this->isEncryptedCastable($key)) { + } elseif ($this->isEncryptedCastable($key) && ! empty(static::currentEncrypter()->getPreviousKeys())) { return false; } elseif ($this->hasCast($key, static::$primitiveCastTypes)) { return $this->castAttribute($key, $attribute) === @@ -2155,7 +2165,10 @@ public function originalIsEquivalent($key) } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsEnumArrayObject::class, AsEnumCollection::class])) { return $this->fromJson($attribute) === $this->fromJson($original); } elseif ($this->isClassCastable($key) && $original !== null && Str::startsWith($this->getCasts()[$key], [AsEncryptedArrayObject::class, AsEncryptedCollection::class])) { - // return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original); + if (empty(static::currentEncrypter()->getPreviousKeys())) { + return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original); + } + return false; } diff --git a/tests/Integration/Database/EloquentModelEncryptedDirtyTest.php b/tests/Integration/Database/EloquentModelEncryptedDirtyTest.php index aa56ee49b8de..720e14a0e91c 100644 --- a/tests/Integration/Database/EloquentModelEncryptedDirtyTest.php +++ b/tests/Integration/Database/EloquentModelEncryptedDirtyTest.php @@ -9,7 +9,7 @@ class EloquentModelEncryptedDirtyTest extends TestCase { - public function testDirtyAttributeBehavior() + public function testDirtyAttributeBehaviorWithNoPreviousKeys() { config(['app.key' => str_repeat('a', 32)]); Model::$encrypter = null; @@ -27,6 +27,37 @@ public function testDirtyAttributeBehavior() $model->secret = 'some-secret'; $model->secret_array_object = [1, 2, 3]; + // Encrypted attributes should always be considered dirty if updated in any way because of rotatable encryption keys... + $this->assertFalse($model->isDirty('secret')); + $this->assertFalse($model->isDirty('secret_array_object')); + + $model->secret = 'some-other-secret'; + $model->secret_array_object = [4, 5, 6]; + + // Encrypted attributes should always be considered dirty if updated in any way because of rotatable encryption keys... + $this->assertTrue($model->isDirty('secret')); + $this->assertTrue($model->isDirty('secret_array_object')); + } + + public function testDirtyAttributeBehaviorWithPreviousKeys() + { + config(['app.key' => str_repeat('a', 32)]); + config(['app.previous_keys' => [str_repeat('b', 32)]]); + Model::$encrypter = null; + + $model = new EncryptedDirtyAttributeCast([ + 'secret' => 'some-secret', + 'secret_array_object' => [1, 2, 3], + ]); + + $model->syncOriginal(); + + $this->assertFalse($model->isDirty('secret')); + $this->assertFalse($model->isDirty('secret_array_object')); + + $model->secret = 'some-secret'; + $model->secret_array_object = [1, 2, 3]; + // Encrypted attributes should always be considered dirty if updated in any way because of rotatable encryption keys... $this->assertTrue($model->isDirty('secret')); $this->assertTrue($model->isDirty('secret_array_object'));