Skip to content

Commit b2d1b2b

Browse files
committed
feat: add support for encrypted boolean
1 parent e80b6d0 commit b2d1b2b

File tree

4 files changed

+174
-6
lines changed

4 files changed

+174
-6
lines changed

MIGRATING.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
## To Laravel Encrypted Casting
44
The main difference between this package and [Laravel Encrypted Casting](https://laravel.com/docs/10.x/eloquent-mutators#encrypted-casting) is that this package serializes the data before encrypting it, while Laravel Encrypted Casting encrypts the data directly. This means that the data is not compatible between the two packages. In order to migrate from this package to Laravel Encrypted Casting, you will need to decrypt the data and then re-encrypt it using Laravel Encrypted Casting. Here is a step-by-step guide on how to do this:
55

6-
[//]: # (TODO: What to do when you need encrypted serialized data?)
7-
86
1. Make sure you're running on Laravel 11 or higher.
97
2. Remove the `Swis\Laravel\Encrypted\EncryptedModel` from your models and replace it with `Illuminate\Database\Eloquent\Model`:
108
```diff
@@ -23,17 +21,20 @@ The main difference between this package and [Laravel Encrypted Casting](https:/
2321
+ 'secret' => 'encrypted',
2422
+ ];
2523
```
26-
4. If you're using encrypted date(time)s, use the custom casts provided by this package:
24+
4. If you're using encrypted booleans or date(time)s, use the custom casts provided by this package:
2725
```diff
2826
- protected $encrypted = [
29-
- 'secret',
27+
- 'secret_boolean',
28+
- 'secret_datetime',
3029
- ];
3130
-
3231
- protected $casts = [
33-
- 'secret' => 'datetime',
32+
- 'secret_boolean' => 'bool',
33+
- 'secret_datetime' => 'datetime',
3434
- ];
3535
+ protected $casts = [
36-
+ 'secret' => \Swis\Laravel\Encrypted\Casts\AsEncryptedDateTime::class,
36+
+ 'secret_boolean' => \Swis\Laravel\Encrypted\Casts\AsEncryptedBoolean::class,
37+
+ 'secret_datetime' => \Swis\Laravel\Encrypted\Casts\AsEncryptedDateTime::class,
3738
+ ];
3839
```
3940
5. Set up our custom model encrypter in your `AppServiceProvider`:

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ composer require swisnl/laravel-encrypted-data
2929
3030
You can use the Eloquent casts provided by this package and everything will be encrypted/decrypted under the hood!
3131

32+
#### Boolean
33+
34+
```php
35+
protected $casts = [
36+
'boolean' => \Swis\Laravel\Encrypted\Casts\AsEncryptedBoolean::class,
37+
];
38+
```
39+
3240
#### Datetime
3341

3442
```php

src/Casts/AsEncryptedBoolean.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Swis\Laravel\Encrypted\Casts;
6+
7+
use Illuminate\Contracts\Database\Eloquent\Castable;
8+
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
9+
use Illuminate\Database\Eloquent\Model;
10+
use Illuminate\Support\Facades\Crypt;
11+
12+
class AsEncryptedBoolean implements Castable
13+
{
14+
/**
15+
* @param string[] $arguments
16+
*
17+
* @return \Illuminate\Contracts\Database\Eloquent\CastsAttributes<bool, bool>
18+
*/
19+
public static function castUsing(array $arguments): CastsAttributes
20+
{
21+
return new class implements CastsAttributes {
22+
/**
23+
* @param string|null $value
24+
* @param array<string, mixed> $attributes
25+
*/
26+
public function get(Model $model, string $key, mixed $value, array $attributes): ?bool
27+
{
28+
if ($value === null) {
29+
return null;
30+
}
31+
32+
return (bool) ($model::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false);
33+
}
34+
35+
/**
36+
* @param bool|null $value
37+
* @param array<string, mixed> $attributes
38+
*/
39+
public function set(Model $model, string $key, #[\SensitiveParameter] mixed $value, array $attributes): ?string
40+
{
41+
if ($value === null) {
42+
return null;
43+
}
44+
45+
return ($model::$encrypter ?? Crypt::getFacadeRoot())->encrypt((string) (int) $value, false);
46+
}
47+
48+
/**
49+
* @param string|null $original
50+
* @param string|null $value
51+
*/
52+
public function compare(Model $model, string $key, mixed $original, mixed $value): bool
53+
{
54+
if (!empty(($model::$encrypter ?? Crypt::getFacadeRoot())->getPreviousKeys())) {
55+
return false;
56+
}
57+
58+
return $this->get($model, $key, $original, []) === $this->get($model, $key, $value, []);
59+
}
60+
};
61+
}
62+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace Swis\Laravel\Encrypted\Tests\Casts;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Swis\Laravel\Encrypted\Casts\AsEncryptedBoolean;
7+
use Swis\Laravel\Encrypted\Tests\_mocks\DummyEncrypter;
8+
use Swis\Laravel\Encrypted\Tests\_mocks\Model;
9+
10+
class AsEncryptedBooleanTest extends TestCase
11+
{
12+
protected function setUp(): void
13+
{
14+
parent::setUp();
15+
Model::$encrypter = new DummyEncrypter();
16+
}
17+
18+
public function testGetReturnsNullForNull(): void
19+
{
20+
$cast = AsEncryptedBoolean::castUsing([]);
21+
$model = new Model();
22+
23+
$result = $cast->get($model, 'flag', null, []);
24+
25+
$this->assertNull($result);
26+
}
27+
28+
public function testGetReturnsBoolean(): void
29+
{
30+
$cast = AsEncryptedBoolean::castUsing([]);
31+
$model = new Model();
32+
33+
$encryptedTrue = Model::$encrypter->encrypt('1', false);
34+
$encryptedFalse = Model::$encrypter->encrypt('0', false);
35+
36+
$this->assertTrue($cast->get($model, 'flag', $encryptedTrue, []));
37+
$this->assertFalse($cast->get($model, 'flag', $encryptedFalse, []));
38+
}
39+
40+
public function testSetReturnsNullForNull(): void
41+
{
42+
$cast = AsEncryptedBoolean::castUsing([]);
43+
$model = new Model();
44+
45+
$result = $cast->set($model, 'flag', null, []);
46+
47+
$this->assertNull($result);
48+
}
49+
50+
public function testSetReturnsEncryptedString(): void
51+
{
52+
$cast = AsEncryptedBoolean::castUsing([]);
53+
$model = new Model();
54+
55+
$encrypted = $cast->set($model, 'flag', true, []);
56+
$this->assertEquals(Model::$encrypter->encrypt('1', false), $encrypted);
57+
58+
$encrypted = $cast->set($model, 'flag', false, []);
59+
$this->assertEquals(Model::$encrypter->encrypt('0', false), $encrypted);
60+
}
61+
62+
public function testCompareReturnsTrueForSameDecryptedValue(): void
63+
{
64+
$cast = AsEncryptedBoolean::castUsing([]);
65+
$model = new Model();
66+
67+
$encryptedTrue = Model::$encrypter->encrypt('1', false);
68+
$encryptedTrue2 = Model::$encrypter->encrypt('1', false);
69+
70+
$this->assertTrue($cast->compare($model, 'flag', $encryptedTrue, $encryptedTrue2));
71+
}
72+
73+
public function testCompareReturnsFalseForDifferentDecryptedValue(): void
74+
{
75+
$cast = AsEncryptedBoolean::castUsing([]);
76+
$model = new Model();
77+
78+
$encryptedTrue = Model::$encrypter->encrypt('1', false);
79+
$encryptedFalse = Model::$encrypter->encrypt('0', false);
80+
81+
$this->assertFalse($cast->compare($model, 'flag', $encryptedTrue, $encryptedFalse));
82+
}
83+
84+
public function testCompareReturnsFalseIfPreviousKeysExist(): void
85+
{
86+
$cast = AsEncryptedBoolean::castUsing([]);
87+
$model = new Model();
88+
89+
// Simulate previous keys
90+
/* @noinspection PhpUndefinedMethodInspection */
91+
$model::$encrypter->setPreviousKeys(['dummy-key']);
92+
93+
$encrypted = Model::$encrypter->encrypt('1', false);
94+
95+
$this->assertFalse($cast->compare($model, 'flag', $encrypted, $encrypted));
96+
}
97+
}

0 commit comments

Comments
 (0)