Skip to content

Commit

Permalink
[9.x] Add support to detect dirty encrypted model attributes (#42888)
Browse files Browse the repository at this point in the history
* Add encypted casts

* Style CI changes

* Bugfix “The payload is invalid.”

Only compare if the original is not null
  • Loading branch information
PhiloNL authored Jun 22, 2022
1 parent 611c0f5 commit 63e22b0
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Casts\AsCollection;
use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject;
use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\InvalidCastException;
use Illuminate\Database\Eloquent\JsonEncodingException;
Expand Down Expand Up @@ -1960,6 +1962,8 @@ public function originalIsEquivalent($key)
$this->castAttribute($key, $original);
} elseif ($this->isClassCastable($key) && in_array($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) {
return $this->fromJson($attribute) === $this->fromJson($original);
} elseif ($this->isClassCastable($key) && $original !== null && in_array($this->getCasts()[$key], [AsEncryptedArrayObject::class, AsEncryptedCollection::class])) {
return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original);
}

return is_numeric($attribute) && is_numeric($original)
Expand Down
92 changes: 92 additions & 0 deletions tests/Database/DatabaseEloquentModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Exception;
use Foo\Bar\EloquentModelNamespacedStub;
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Connection;
use Illuminate\Database\ConnectionResolverInterface;
Expand All @@ -16,6 +17,8 @@
use Illuminate\Database\Eloquent\Casts\ArrayObject;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Casts\AsCollection;
use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject;
use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection;
use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\JsonEncodingException;
Expand All @@ -28,6 +31,7 @@
use Illuminate\Database\Query\Processors\Processor;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\InteractsWithTime;
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
Expand Down Expand Up @@ -208,6 +212,92 @@ public function testDirtyOnCastedStringable()
$this->assertTrue($model->isDirty('asStringableAttribute'));
}

public function testDirtyOnCastedEncryptedCollection()
{
$this->encrypter = m::mock(Encrypter::class);
Crypt::swap($this->encrypter);
Model::$encrypter = null;

$this->encrypter->expects('encryptString')
->twice()
->with('{"foo":"bar"}')
->andReturn('encrypted-value');

$this->encrypter->expects('decryptString')
->with('encrypted-value')
->andReturn('{"foo": "bar"}');

$this->encrypter->expects('encryptString')
->with('{"foo":"baz"}')
->andReturn('new-encrypted-value');

$this->encrypter->expects('decrypt')
->with('encrypted-value', false)
->andReturn('{"foo": "bar"}');

$this->encrypter->expects('decrypt')
->with('new-encrypted-value', false)
->andReturn('{"foo":"baz"}');

$model = new EloquentModelCastingStub;
$model->setRawAttributes([
'asEncryptedCollectionAttribute' => 'encrypted-value',
]);
$model->syncOriginal();

$this->assertInstanceOf(BaseCollection::class, $model->asEncryptedCollectionAttribute);
$this->assertFalse($model->isDirty('asEncryptedCollectionAttribute'));

$model->asEncryptedCollectionAttribute = ['foo' => 'bar'];
$this->assertFalse($model->isDirty('asEncryptedCollectionAttribute'));

$model->asEncryptedCollectionAttribute = ['foo' => 'baz'];
$this->assertTrue($model->isDirty('asEncryptedCollectionAttribute'));
}

public function testDirtyOnCastedEncryptedArrayObject()
{
$this->encrypter = m::mock(Encrypter::class);
Crypt::swap($this->encrypter);
Model::$encrypter = null;

$this->encrypter->expects('encryptString')
->twice()
->with('{"foo":"bar"}')
->andReturn('encrypted-value');

$this->encrypter->expects('decryptString')
->with('encrypted-value')
->andReturn('{"foo": "bar"}');

$this->encrypter->expects('encryptString')
->with('{"foo":"baz"}')
->andReturn('new-encrypted-value');

$this->encrypter->expects('decrypt')
->with('encrypted-value', false)
->andReturn('{"foo": "bar"}');

$this->encrypter->expects('decrypt')
->with('new-encrypted-value', false)
->andReturn('{"foo":"baz"}');

$model = new EloquentModelCastingStub;
$model->setRawAttributes([
'asEncryptedArrayObjectAttribute' => 'encrypted-value',
]);
$model->syncOriginal();

$this->assertInstanceOf(ArrayObject::class, $model->asEncryptedArrayObjectAttribute);
$this->assertFalse($model->isDirty('asEncryptedArrayObjectAttribute'));

$model->asEncryptedArrayObjectAttribute = ['foo' => 'bar'];
$this->assertFalse($model->isDirty('asEncryptedArrayObjectAttribute'));

$model->asEncryptedArrayObjectAttribute = ['foo' => 'baz'];
$this->assertTrue($model->isDirty('asEncryptedArrayObjectAttribute'));
}

public function testCleanAttributes()
{
$model = new EloquentModelStub(['foo' => '1', 'bar' => 2, 'baz' => 3]);
Expand Down Expand Up @@ -2690,6 +2780,8 @@ class EloquentModelCastingStub extends Model
'asarrayobjectAttribute' => AsArrayObject::class,
'ascollectionAttribute' => AsCollection::class,
'asStringableAttribute' => AsStringable::class,
'asEncryptedCollectionAttribute' => AsEncryptedCollection::class,
'asEncryptedArrayObjectAttribute' => AsEncryptedArrayObject::class,
];

public function jsonAttributeValue()
Expand Down

0 comments on commit 63e22b0

Please sign in to comment.