Skip to content

Commit

Permalink
[8.x] Add encrypted string Eloquent cast (#34937)
Browse files Browse the repository at this point in the history
* Add encrypted string Eloquent cast

Co-authored-by: Jess Archer <[email protected]>

* Symmetry and micro-optimization

Co-authored-by: Jess Archer <[email protected]>
  • Loading branch information
jasonmccreary and jessarcher authored Oct 23, 2020
1 parent 11d3994 commit 1aa3175
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 1 deletion.
45 changes: 44 additions & 1 deletion src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Str;
use InvalidArgumentException;
Expand Down Expand Up @@ -70,6 +71,7 @@ trait HasAttributes
'datetime',
'decimal',
'double',
'encrypted',
'float',
'int',
'integer',
Expand Down Expand Up @@ -552,6 +554,8 @@ 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 @@ -674,7 +678,7 @@ public function setAttribute($key, $value)
return $this;
}

if ($this->isJsonCastable($key) && ! is_null($value)) {
if (! is_null($value) && $this->isJsonCastable($key)) {
$value = $this->castAttributeAsJson($key, $value);
}

Expand All @@ -685,6 +689,10 @@ public function setAttribute($key, $value)
return $this->fillJsonAttribute($key, $value);
}

if (! is_null($value) && $this->isEncryptedCastable($key)) {
$value = $this->castAttributeAsEncryptedString($key, $value);
}

$this->attributes[$key] = $value;

return $this;
Expand Down Expand Up @@ -848,6 +856,30 @@ public function fromJson($value, $asObject = false)
return json_decode($value, ! $asObject);
}

/**
* Decode the given encrypted string.
*
* @param string $value
* @param bool $asObject
* @return mixed
*/
public function fromEncryptedString($value)
{
return Crypt::decryptString($value);
}

/**
* Cast the given attribute to an encrypted string.
*
* @param string $key
* @param mixed $value
* @return string
*/
protected function castAttributeAsEncryptedString($key, $value)
{
return Crypt::encryptString($value);
}

/**
* Decode the given float.
*
Expand Down Expand Up @@ -1083,6 +1115,17 @@ protected function isJsonCastable($key)
return $this->hasCast($key, ['array', 'json', 'object', 'collection']);
}

/**
* Determine whether a value is an encrypted castable for inbound manipulation.
*
* @param string $key
* @return bool
*/
protected function isEncryptedCastable($key)
{
return $this->hasCast($key, ['encrypted']);
}

/**
* Determine if the given key is cast using a custom class.
*
Expand Down
64 changes: 64 additions & 0 deletions tests/Integration/Database/EloquentModelEncryptedCastingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Illuminate\Tests\Integration\Database;

use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Schema;

/**
* @group integration
*/
class EloquentModelEncryptedCastingTest extends DatabaseTestCase
{
protected $encrypter;

protected function setUp(): void
{
parent::setUp();

$this->encrypter = $this->mock(Encrypter::class);
Crypt::swap($this->encrypter);

Schema::create('encrypted_casts', function (Blueprint $table) {
$table->increments('id');
$table->string('secret', 1000)->nullable();
});
}

public function testStringsAreCastable()
{
$this->encrypter->expects('encryptString')
->with('this is a secret string')
->andReturn('encrypted-secret-string');
$this->encrypter->expects('decryptString')
->with('encrypted-secret-string')
->andReturn('this is a secret string');

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

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

/**
* @property $secret
*/
class EncryptedCast extends Model
{
public $timestamps = false;
protected $guarded = [];

public $casts = [
'secret' => 'encrypted',
];
}

0 comments on commit 1aa3175

Please sign in to comment.