Skip to content

Commit

Permalink
Add types for casting encrypted strings to objects
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonmccreary committed Oct 23, 2020
1 parent dc08cba commit 1cf5513
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 7 deletions.
18 changes: 14 additions & 4 deletions src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ trait HasAttributes
'decimal',
'double',
'encrypted',
'encrypted:array',
'encrypted:collection',
'encrypted:json',
'encrypted:object',
'float',
'int',
'integer',
Expand Down Expand Up @@ -525,6 +529,14 @@ protected function castAttribute($key, $value)
return $value;
}

// If the key is one of the encrypted castable types, we'll first decrypt
// the value and update the cast type so we may leverage the following
// logic for casting the value to any additionally specified type.
if ($this->isEncryptedCastable($key)) {
$value = $this->fromEncryptedString($value);
$castType = Str::after($castType, 'encrypted:');
}

switch ($castType) {
case 'int':
case 'integer':
Expand Down Expand Up @@ -554,8 +566,6 @@ protected function castAttribute($key, $value)
return $this->asDateTime($value);
case 'timestamp':
return $this->asTimestamp($value);
case 'encrypted':
return $this->fromEncryptedString($value);
}

if ($this->isClassCastable($key)) {
Expand Down Expand Up @@ -1112,7 +1122,7 @@ protected function isDateCastable($key)
*/
protected function isJsonCastable($key)
{
return $this->hasCast($key, ['array', 'json', 'object', 'collection']);
return $this->hasCast($key, ['array', 'json', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
}

/**
Expand All @@ -1123,7 +1133,7 @@ protected function isJsonCastable($key)
*/
protected function isEncryptedCastable($key)
{
return $this->hasCast($key, ['encrypted']);
return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
}

/**
Expand Down
110 changes: 107 additions & 3 deletions tests/Integration/Database/EloquentModelEncryptedCastingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Schema;

Expand All @@ -25,6 +26,10 @@ protected function setUp(): void
Schema::create('encrypted_casts', function (Blueprint $table) {
$table->increments('id');
$table->string('secret', 1000)->nullable();
$table->text('secret_array')->nullable();
$table->text('secret_json')->nullable();
$table->text('secret_object')->nullable();
$table->text('secret_collection')->nullable();
});
}

Expand All @@ -37,21 +42,116 @@ public function testStringsAreCastable()
->with('encrypted-secret-string')
->andReturn('this is a secret string');

/** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
$subject = EncryptedCast::create([
'secret' => 'this is a secret string',
]);

$this->assertSame('this is a secret string', $subject->secret);
$this->assertDatabaseHas('encrypted_casts', [
'id' => $subject->id,
'secret' => 'encrypted-secret-string',
]);
}

public function testArraysAreCastable()
{
$this->encrypter->expects('encryptString')
->with('{"key1":"value1"}')
->andReturn('encrypted-secret-array-string');
$this->encrypter->expects('decryptString')
->with('encrypted-secret-array-string')
->andReturn('{"key1":"value1"}');

/** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
$subject = EncryptedCast::create([
'secret_array' => ['key1' => 'value1'],
]);

$this->assertSame(['key1' => 'value1'], $subject->secret_array);
$this->assertDatabaseHas('encrypted_casts', [
'id' => $subject->id,
'secret_array' => 'encrypted-secret-array-string',
]);
}

public function testJsonIsCastable()
{
$this->encrypter->expects('encryptString')
->with('{"key1":"value1"}')
->andReturn('encrypted-secret-json-string');
$this->encrypter->expects('decryptString')
->with('encrypted-secret-json-string')
->andReturn('{"key1":"value1"}');

/** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
$subject = EncryptedCast::create([
'secret_json' => ['key1' => 'value1'],
]);

$this->assertSame(['key1' => 'value1'], $subject->secret_json);
$this->assertDatabaseHas('encrypted_casts', [
'id' => $subject->id,
'secret_json' => 'encrypted-secret-json-string',
]);
}

public function testObjectIsCastable()
{
$object = new \stdClass();
$object->key1 = 'value1';

$this->encrypter->expects('encryptString')
->with('{"key1":"value1"}')
->andReturn('encrypted-secret-object-string');
$this->encrypter->expects('decryptString')
->twice()
->with('encrypted-secret-object-string')
->andReturn('{"key1":"value1"}');

/** @var \Illuminate\Tests\Integration\Database\EncryptedCast $object */
$object = EncryptedCast::create([
'secret' => 'this is a secret string',
'secret_object' => $object,
]);

$this->assertSame('this is a secret string', $object->secret);
$this->assertInstanceOf(\stdClass::class, $object->secret_object);
$this->assertSame('value1', $object->secret_object->key1);
$this->assertDatabaseHas('encrypted_casts', [
'id' => $object->id,
'secret' => 'encrypted-secret-string',
'secret_object' => 'encrypted-secret-object-string',
]);
}

public function testCollectionIsCastable()
{
$this->encrypter->expects('encryptString')
->with('{"key1":"value1"}')
->andReturn('encrypted-secret-collection-string');
$this->encrypter->expects('decryptString')
->twice()
->with('encrypted-secret-collection-string')
->andReturn('{"key1":"value1"}');

/** @var \Illuminate\Tests\Integration\Database\EncryptedCast $subject */
$subject = EncryptedCast::create([
'secret_collection' => new Collection(['key1' => 'value1']),
]);

$this->assertInstanceOf(Collection::class, $subject->secret_collection);
$this->assertSame('value1', $subject->secret_collection->get('key1'));
$this->assertDatabaseHas('encrypted_casts', [
'id' => $subject->id,
'secret_collection' => 'encrypted-secret-collection-string',
]);
}
}

/**
* @property $secret
* @property $secret_array
* @property $secret_json
* @property $secret_object
* @property $secret_collection
*/
class EncryptedCast extends Model
{
Expand All @@ -60,5 +160,9 @@ class EncryptedCast extends Model

public $casts = [
'secret' => 'encrypted',
'secret_array' => 'encrypted:array',
'secret_json' => 'encrypted:json',
'secret_object' => 'encrypted:object',
'secret_collection' => 'encrypted:collection',
];
}

0 comments on commit 1cf5513

Please sign in to comment.