From aff315135c295061be7225180ae859ec1f588ab4 Mon Sep 17 00:00:00 2001 From: DarkGhosthunter Date: Tue, 30 Nov 2021 16:34:34 -0300 Subject: [PATCH] Fixes AsEncryptedObject traits not respecting nullable columns. --- .../Eloquent/Casts/AsEncryptedArrayObject.php | 14 ++- .../Eloquent/Casts/AsEncryptedCollection.php | 12 +- .../EloquentModelEncryptedCastingTest.php | 107 ++++++++++++++++++ 3 files changed, 128 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedArrayObject.php b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedArrayObject.php index 5918bc1b2203..68273a5e5240 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedArrayObject.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedArrayObject.php @@ -20,17 +20,25 @@ public static function castUsing(array $arguments) { public function get($model, $key, $value, $attributes) { - return new ArrayObject(json_decode(Crypt::decryptString($attributes[$key]), true)); + if (isset($attributes[$key])) { + return new ArrayObject(json_decode(Crypt::decryptString($attributes[$key]), true)); + } + + return null; } public function set($model, $key, $value, $attributes) { - return [$key => Crypt::encryptString(json_encode($value))]; + if ($value !== null) { + return [$key => Crypt::encryptString(json_encode($value))]; + } + + return null; } public function serialize($model, string $key, $value, array $attributes) { - return $value->getArrayCopy(); + return $value !== null ? $value->getArrayCopy() : null; } }; } diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php index ad11b1787b38..0021861e1254 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEncryptedCollection.php @@ -21,12 +21,20 @@ public static function castUsing(array $arguments) { public function get($model, $key, $value, $attributes) { - return new Collection(json_decode(Crypt::decryptString($attributes[$key]), true)); + if (isset($attributes[$key])) { + return new Collection(json_decode(Crypt::decryptString($attributes[$key]), true)); + } + + return null; } public function set($model, $key, $value, $attributes) { - return [$key => Crypt::encryptString(json_encode($value))]; + if ($value !== null) { + return [$key => Crypt::encryptString(json_encode($value))]; + } + + return null; } }; } diff --git a/tests/Integration/Database/EloquentModelEncryptedCastingTest.php b/tests/Integration/Database/EloquentModelEncryptedCastingTest.php index 798ede77fe0c..cebcbdd075ea 100644 --- a/tests/Integration/Database/EloquentModelEncryptedCastingTest.php +++ b/tests/Integration/Database/EloquentModelEncryptedCastingTest.php @@ -3,6 +3,9 @@ namespace Illuminate\Tests\Integration\Database; use Illuminate\Contracts\Encryption\Encrypter; +use Illuminate\Database\Eloquent\Casts\ArrayObject; +use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject; +use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Collection; @@ -178,6 +181,110 @@ public function testCollectionIsCastable() ]); } + public function testAsEncryptedCollection() + { + $this->encrypter->expects('encryptString') + ->twice() + ->with('{"key1":"value1"}') + ->andReturn('encrypted-secret-collection-string-1'); + $this->encrypter->expects('encryptString') + ->times(12) + ->with('{"key1":"value1","key2":"value2"}') + ->andReturn('encrypted-secret-collection-string-2'); + $this->encrypter->expects('decryptString') + ->once() + ->with('encrypted-secret-collection-string-2') + ->andReturn('{"key1":"value1","key2":"value2"}'); + + $subject = new EncryptedCast; + + $subject->mergeCasts(['secret_collection' => AsEncryptedCollection::class]); + + $subject->secret_collection = new Collection(['key1' => 'value1']); + $subject->secret_collection->put('key2', 'value2'); + + $subject->save(); + + $this->assertInstanceOf(Collection::class, $subject->secret_collection); + $this->assertSame('value1', $subject->secret_collection->get('key1')); + $this->assertSame('value2', $subject->secret_collection->get('key2')); + $this->assertDatabaseHas('encrypted_casts', [ + 'id' => $subject->id, + 'secret_collection' => 'encrypted-secret-collection-string-2', + ]); + + $subject = $subject->fresh(); + + $this->assertInstanceOf(Collection::class, $subject->secret_collection); + $this->assertSame('value1', $subject->secret_collection->get('key1')); + $this->assertSame('value2', $subject->secret_collection->get('key2')); + + $subject->secret_collection = null; + $subject->save(); + + $this->assertNull($subject->secret_collection); + $this->assertDatabaseHas('encrypted_casts', [ + 'id' => $subject->id, + 'secret_collection' => null, + ]); + + $this->assertNull($subject->fresh()->secret_collection); + } + + public function testAsEncryptedArrayObject() + { + $this->encrypter->expects('encryptString') + ->once() + ->with('{"key1":"value1"}') + ->andReturn('encrypted-secret-array-string-1'); + $this->encrypter->expects('decryptString') + ->once() + ->with('encrypted-secret-array-string-1') + ->andReturn('{"key1":"value1"}'); + $this->encrypter->expects('encryptString') + ->times(12) + ->with('{"key1":"value1","key2":"value2"}') + ->andReturn('encrypted-secret-array-string-2'); + $this->encrypter->expects('decryptString') + ->once() + ->with('encrypted-secret-array-string-2') + ->andReturn('{"key1":"value1","key2":"value2"}'); + + $subject = new EncryptedCast; + + $subject->mergeCasts(['secret_array' => AsEncryptedArrayObject::class]); + + $subject->secret_array = ['key1' => 'value1']; + $subject->secret_array['key2'] = 'value2'; + + $subject->save(); + + $this->assertInstanceOf(ArrayObject::class, $subject->secret_array); + $this->assertSame('value1', $subject->secret_array['key1']); + $this->assertSame('value2', $subject->secret_array['key2']); + $this->assertDatabaseHas('encrypted_casts', [ + 'id' => $subject->id, + 'secret_array' => 'encrypted-secret-array-string-2', + ]); + + $subject = $subject->fresh(); + + $this->assertInstanceOf(ArrayObject::class, $subject->secret_array); + $this->assertSame('value1', $subject->secret_array['key1']); + $this->assertSame('value2', $subject->secret_array['key2']); + + $subject->secret_array = null; + $subject->save(); + + $this->assertNull($subject->secret_array); + $this->assertDatabaseHas('encrypted_casts', [ + 'id' => $subject->id, + 'secret_array' => null, + ]); + + $this->assertNull($subject->fresh()->secret_array); + } + public function testCustomEncrypterCanBeSpecified() { $customEncrypter = $this->mock(Encrypter::class);