Compare commits

...

10 Commits

Author SHA1 Message Date
Bjorn Voesten 9193ff649a
Merge pull request #6 from pgtruesdell/master
PHP 8 Support and Fixed Tests
2021-03-21 15:30:04 +01:00
Paul Truesdell, II 931673fd29 Added support for PHP 8, fixed and upgraded PHPUnit config.
- Added support for PHP 8
- Fixed the phpunit.xml configuration, removed the features test suite.
- Upgraded the PHPUnit configuration with “—migrate-configuration” as recommended by the PHPUnit 9 deprecation warning.
2021-03-18 12:42:26 -04:00
Bjorn Voesten 921f1df2ad
Merge pull request #4 from kaiser-software/feature/add-after-support-for-blueprint-macros
Added after column support for blueprint macro
2021-01-22 01:09:21 +01:00
Bjorn Voesten 5fdadf1b46
Merge pull request #5 from bjornvoesten/development
Added nullable attributes
2021-01-22 00:23:16 +01:00
Bjorn Voesten 250e4c7352 Added nullable attributes 2021-01-22 00:17:35 +01:00
Rick Goemans f91b31717d Added after column support for blueprint macros and prevented duplicated code 2021-01-20 18:08:47 +01:00
Bjorn Voesten 13bfb5e4ee
Merge pull request #2 from bjornvoesten/development
Added whereIn clause
2020-12-25 01:38:47 +01:00
Bjorn Voesten 278719a552 Added whereIn clause 2020-12-25 01:37:58 +01:00
Bjorn Voesten ddf24dd78f
Merge pull request #1 from wdavis/master
Installation tweaks
2020-12-04 22:40:57 +01:00
wdavis dfce155a77 Fix config publishing 2020-12-03 23:44:58 -06:00
13 changed files with 1535 additions and 578 deletions

View File

@ -20,7 +20,7 @@
} }
}, },
"require": { "require": {
"php": "^7.2.5", "php": "^7.2.5|^8.0",
"illuminate/config": "^7.0|^8.0", "illuminate/config": "^7.0|^8.0",
"illuminate/support": "^7.0|^8.0", "illuminate/support": "^7.0|^8.0",
"illuminate/database": "^7.0|^8.0", "illuminate/database": "^7.0|^8.0",

1639
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false">
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd" <coverage processUncoveredFiles="true">
backupGlobals="false" <include>
backupStaticAttributes="false" <directory suffix=".php">./src</directory>
bootstrap="vendor/autoload.php" </include>
colors="true" </coverage>
convertErrorsToExceptions="true" <testsuites>
convertNoticesToExceptions="true" <testsuite name="Unit">
convertWarningsToExceptions="true" <directory suffix="Test.php">./tests/Unit</directory>
processIsolation="false" </testsuite>
stopOnFailure="false"> </testsuites>
<testsuites> <php>
<testsuite name="Unit"> <server name="APP_ENV" value="testing"/>
<directory suffix="Test.php">./tests/Unit</directory> <server name="BCRYPT_ROUNDS" value="4"/>
</testsuite> <server name="CACHE_DRIVER" value="array"/>
<server name="MAIL_DRIVER" value="array"/>
<testsuite name="Feature"> <server name="QUEUE_CONNECTION" value="sync"/>
<directory suffix="Test.php">./tests/Feature</directory> <server name="SESSION_DRIVER" value="file"/>
</testsuite> <server name="DB_CONNECTION" value="testing"/>
</testsuites> </php>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="file"/>
<server name="DB_CONNECTION" value="testing"/>
</php>
</phpunit> </phpunit>

View File

@ -1,12 +1,14 @@
# CipherSweet for Laravel # CipherSweet for Laravel
A Laravel implementation of [Paragon Initiative Enterprises CipherSweet](https://ciphersweet.paragonie.com) searchable field level encryption. A Laravel implementation of [Paragon Initiative Enterprises CipherSweet](https://ciphersweet.paragonie.com) searchable
field level encryption.
Make sure you have some basic understanding of CipherSweet before continuing. Make sure you have some basic understanding of CipherSweet before continuing.
## Installation ## Installation
Install the package using composer: Install the package using composer:
``` ```
composer require bjorn-voesten/ciphersweet-for-laravel composer require bjorn-voesten/ciphersweet-for-laravel
``` ```
@ -16,10 +18,13 @@ The package will then automatically register itself.
#### Encryption key #### Encryption key
In your `.env` file you should add: In your `.env` file you should add:
```dotenv ```dotenv
CIPHERSWEET_KEY= CIPHERSWEET_KEY=
``` ```
And then generate an encryption key: And then generate an encryption key:
``` ```
php artisan ciphersweet:key php artisan ciphersweet:key
``` ```
@ -27,6 +32,7 @@ php artisan ciphersweet:key
#### Config file #### Config file
Publish the config file: Publish the config file:
``` ```
php artisan vendor:publish --tag=ciphersweet-config php artisan vendor:publish --tag=ciphersweet-config
``` ```
@ -35,8 +41,9 @@ php artisan vendor:publish --tag=ciphersweet-config
### Define encryption ### Define encryption
Add the `BjornVoesten\CipherSweet\Concerns\WithAttributeEncryption` trait to your model <br> Add the `BjornVoesten\CipherSweet\Concerns\WithAttributeEncryption` trait to your model <br>
and add the `BjornVoesten\CipherSweet\Casts\Encrypted` cast to the attributes you want to encrypt. and add the `BjornVoesten\CipherSweet\Casts\Encrypted` cast to the attributes you want to encrypt.
```php ```php
<?php <?php
@ -99,29 +106,42 @@ class User extends Model
Attributes will be automatically encrypted and decrypted when filling and retrieving attribute values. Attributes will be automatically encrypted and decrypted when filling and retrieving attribute values.
**Note** Because the package uses Laravel casts it is not possible to combine the `Encrypted` cast and accessors/mutators. **Note** Because the package uses Laravel casts it is not possible to combine the `Encrypted` cast and
accessors/mutators.
### Searching ### Searching
**Note** When searching with the `equal to` operator models will be returned when the value is found in one of all available or defined indexes. When searching with the `not equal to` operator all models where the value is not found in any of the available or the defined indexes are returned. **Note** When searching with the `equal to` operator models will be returned when the value is found in one of all
available or defined indexes. When searching with the `not equal to` operator all models where the value is not found in
any of the available or the defined indexes are returned.
**Note** **Note**
Because of the limited search possibilities in CipherSweet only the `=` and `!=` operators are available when searching encrypted attributes. Because of the limited search possibilities in CipherSweet only the `=` and `!=` operators are available when searching
encrypted attributes.
#### `whereEncrypted` <br/>
#### `whereEncrypted`, `orWhereEncrypted`
```php ```php
User::query() User::query()
->whereEncrypted('social_security_number', '=', '123-456-789') ->whereEncrypted('social_security_number', '=', '123-456-789')
->orWhereEncrypted('social_security_number', '=', '123-456-789')
->get(); ->get();
``` ```
#### `orWhereEncrypted` <br/>
#### `whereInEncrypted`, `orWhereInEncrypted`
```php ```php
User::query() User::query()
->whereEncrypted('social_security_number', '=', '123-456-789') ->whereInEncrypted('social_security_number', [
->orWhereEncrypted('social_security_number', '=', '456-123-789') '123-456-789',
])
->orWhereInEncrypted('social_security_number', [
'456-123-789',
])
->get(); ->get();
``` ```
@ -131,7 +151,8 @@ Please see [contributing.md](contributing.md) for details and a todolist.
## Security ## Security
If you discover any security related issues, please email [security@bjornvoesten.com](mailto:security@bjornvoesten.com) instead of using the issue tracker. If you discover any security related issues, please email [security@bjornvoesten.com](mailto:security@bjornvoesten.com)
instead of using the issue tracker.
## Testing ## Testing

View File

@ -13,10 +13,10 @@ class Encrypted implements CastsAttributes
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* @param array $attributes * @param array $attributes
* @return string * @return string|null
* @throws \Exception * @throws \Exception
*/ */
public function get($model, string $key, $value, array $attributes) public function get($model, string $key, $value, array $attributes): ?string
{ {
return $model->decrypt($key); return $model->decrypt($key);
} }

View File

@ -108,23 +108,23 @@ class CipherSweetService
$field = new EncryptedField( $field = new EncryptedField(
$this->engine, $this->engine,
$table = $model->getTable(), $table = $model->getTable(),
$attribute->column, $attribute->column
); );
// Map and add the indexes to the encrypted // Map and add the indexes to the encrypted
// field instance. // field instance.
collect($attribute->indexes) collect($attribute->indexes)
->map(function (Index $index) { ->map(static function (Index $index) {
return $index = new BlindIndex( return new BlindIndex(
$index->column, $index->column,
$index->transformers, $index->transformers,
$index->bits, $index->bits,
$index->fast, $index->fast
); );
}) })
->each( ->each(static function ($index) use ($field) {
fn($index) => $field->addBlindIndex($index) return $field->addBlindIndex($index);
); });
return $field; return $field;
} }
@ -134,18 +134,24 @@ class CipherSweetService
* *
* @param \Illuminate\Database\Eloquent\Model|\BjornVoesten\CipherSweet\Concerns\WithAttributeEncryption $model * @param \Illuminate\Database\Eloquent\Model|\BjornVoesten\CipherSweet\Concerns\WithAttributeEncryption $model
* @param string $attribute * @param string $attribute
* @param string|int|boolean $value * @param string|int|boolean|null $value
* @return array * @return array
* @throws \ParagonIE\CipherSweet\Exception\BlindIndexNameCollisionException * @throws \ParagonIE\CipherSweet\Exception\BlindIndexNameCollisionException
* @throws \ParagonIE\CipherSweet\Exception\BlindIndexNotFoundException * @throws \ParagonIE\CipherSweet\Exception\BlindIndexNotFoundException
* @throws \ParagonIE\CipherSweet\Exception\CryptoOperationException * @throws \ParagonIE\CipherSweet\Exception\CryptoOperationException
* @throws \SodiumException * @throws \SodiumException
*/ */
public function encrypt(Model $model, string $attribute, $value) public function encrypt(Model $model, string $attribute, $value): array
{ {
return $this $field = $this->field($model, $attribute);
->field($model, $attribute)
->prepareForStorage($value); if (is_null($value)) {
return [null, array_map(static function () {
return null;
}, $field->getAllBlindIndexes(''))];
}
return $field->prepareForStorage($value);
} }
/** /**
@ -158,8 +164,12 @@ class CipherSweetService
* @throws \ParagonIE\CipherSweet\Exception\BlindIndexNameCollisionException * @throws \ParagonIE\CipherSweet\Exception\BlindIndexNameCollisionException
* @throws \ParagonIE\CipherSweet\Exception\CryptoOperationException * @throws \ParagonIE\CipherSweet\Exception\CryptoOperationException
*/ */
public function decrypt(Model $model, string $attribute, $value) public function decrypt(Model $model, string $attribute, $value): ?string
{ {
if (is_null($value)) {
return null;
}
return $this return $this
->field($model, $attribute) ->field($model, $attribute)
->decryptValue($value); ->decryptValue($value);

View File

@ -36,7 +36,7 @@ class CipherSweetServiceProvider extends ServiceProvider
{ {
// Config // Config
$this->publishes([ $this->publishes([
__DIR__ . '/../config/ciphersweet.php', __DIR__ . '/../config/ciphersweet.php' => config_path('ciphersweet.php')
], 'ciphersweet-config'); ], 'ciphersweet-config');
$this->mergeConfigFrom( $this->mergeConfigFrom(

View File

@ -3,7 +3,6 @@
namespace BjornVoesten\CipherSweet\Concerns; namespace BjornVoesten\CipherSweet\Concerns;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
/** /**
* @mixin \Illuminate\Database\Eloquent\Model * @mixin \Illuminate\Database\Eloquent\Model
@ -16,9 +15,8 @@ trait WithAttributeEncryption
* @param string $attribute * @param string $attribute
* @param string|int|boolean $value * @param string|int|boolean $value
* @return array * @return array
* @throws \Exception
*/ */
public function encrypt(string $attribute, $value) public function encrypt(string $attribute, $value): array
{ {
[$ciphertext, $indexes] = $result = app('ciphersweet')->encrypt( [$ciphertext, $indexes] = $result = app('ciphersweet')->encrypt(
$this, $attribute, $value $this, $attribute, $value
@ -37,12 +35,12 @@ trait WithAttributeEncryption
* Encrypt the attribute. * Encrypt the attribute.
* *
* @param string $attribute * @param string $attribute
* @return $this * @return string|null
*/ */
public function decrypt(string $attribute) public function decrypt(string $attribute): ?string
{ {
return app('ciphersweet')->decrypt( return app('ciphersweet')->decrypt(
$this, $attribute, $this->attributes[$attribute] $this, $attribute, $this->attributes[$attribute] ?? null
); );
} }
@ -54,27 +52,25 @@ trait WithAttributeEncryption
* @param $operator * @param $operator
* @param $value * @param $value
* @param array $indexes * @param array $indexes
* @param string $boolean
* @return void * @return void
* @throws \Exception
*/ */
public function scopeWhereEncrypted(Builder $query, string $column, $operator, $value, array $indexes = []): void public function scopeWhereEncrypted(Builder $query, string $column, $operator, $value, array $indexes = [], $boolean = 'and'): void
{ {
$available = Arr::last( /** @var array $available */
$this->encrypt($column, $value) $available = $this->encrypt($column, $value)[1];
);
$indexes = empty($indexes) $indexes = empty($indexes) ? array_keys($available) : $indexes;
? array_keys($available)
: $indexes;
$first = true; $method = $boolean === 'or'
foreach ($indexes as $index) { ? 'orWhere'
$first : 'where';
? $query->where($index, $operator, $available[$index])
: $query->orWhere($index, $operator, $available[$index]);
$first = false; $query->{$method}(function (Builder $query) use ($available, $operator, $indexes) {
} foreach ($indexes as $key => $index) {
$query->where($index, $operator, $available[$index]);
}
});
} }
/** /**
@ -83,23 +79,76 @@ trait WithAttributeEncryption
* @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $query
* @param string $column * @param string $column
* @param string $operator * @param string $operator
* @param $value * @param mixed $value
* @param array $indexes * @param array $indexes
* @param string $boolean
* @return void
*/
public function scopeOrWhereEncrypted(
Builder $query, string $column, string $operator, $value,
array $indexes = [], $boolean = 'or'
): void
{
$this->scopeWhereEncrypted(
$query, $column, $operator, $value, $indexes, $boolean
);
}
/**
* Add a where in clause to the query for an encrypted column.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $column
* @param array $values
* @param array $indexes
* @param string $boolean
* @return void * @return void
* @throws \Exception * @throws \Exception
*/ */
public function scopeOrWhereEncrypted(Builder $query, string $column, string $operator, $value, array $indexes = []): void public function scopeWhereInEncrypted(
Builder $query, string $column, array $values, array $indexes = [],
$boolean = 'and'
): void
{ {
$available = Arr::last( $values = array_map(function (string $value) use ($column) {
$this->encrypt($column, $value) return $this->encrypt($column, $value)[1];
}, $values);
$available = array_keys($values[0]);
$indexes = empty($indexes) ? $available : $indexes;
$method = $boolean === 'or'
? 'orWhere'
: 'where';
$query->{$method}(function (Builder $query) use ($values, $indexes) {
foreach ($indexes as $key => $index) {
(bool) $key
? $query->orWhereIn($index, $values)
: $query->whereIn($index, $values);
}
});
}
/**
* Add a or where in clause to the query for an encrypted column.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $column
* @param array $values
* @param array $indexes
* @param string $boolean
* @return void
* @throws \Exception
*/
public function scopeOrWhereInEncrypted(
Builder $query, string $column, array $values, array $indexes = [],
$boolean = 'or'
): void
{
$this->scopeWhereInEncrypted(
$query, $column, $values, $indexes, $boolean
); );
$indexes = empty($indexes)
? array_keys($available)
: $indexes;
foreach ($indexes as $index) {
$query->orWhere($index, $operator, $available[$index]);
}
} }
} }

View File

@ -11,7 +11,7 @@ class Blueprint
{ {
public function encrypted(): Closure public function encrypted(): Closure
{ {
return function (string $name, ?array $indexes = null): void { return function (string $name, ?array $indexes = null, ?string $after = null, bool $nullable = false): void {
$columns = empty($indexes) $columns = empty($indexes)
? [ ? [
$name, $name,
@ -23,7 +23,13 @@ class Blueprint
); );
foreach ($columns as $column) { foreach ($columns as $column) {
$this->string($column); $addedColumn = $this->string($column)->nullable($nullable);
if($after) {
$addedColumn->after($after);
$after = $column;
}
} }
$this->index($columns); $this->index($columns);
@ -32,22 +38,8 @@ class Blueprint
public function nullableEncrypted(): Closure public function nullableEncrypted(): Closure
{ {
return function (string $name, ?array $indexes = null): void { return function (string $name, ?array $indexes = null, ?string $after = null): void {
$columns = empty($indexes) $this->encrypted($name, $indexes, $after, true);
? [
$name,
"{$name}_index",
]
: array_merge(
[$name],
$indexes
);
foreach ($columns as $column) {
$this->string($column)->nullable();
}
$this->index($columns);
}; };
} }
} }

View File

@ -10,8 +10,8 @@ trait CreateUsersTable
protected function createUsersTable(): void protected function createUsersTable(): void
{ {
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
$table->id('id'); $table->id();
$table->string('social_security_number'); $table->string('social_security_number')->nullable();
$table->string('social_security_number_index')->nullable(); $table->string('social_security_number_index')->nullable();
$table->string('custom_index')->nullable(); $table->string('custom_index')->nullable();
$table->timestamps(); $table->timestamps();

View File

@ -9,10 +9,10 @@ trait CreatesUsers
/** /**
* Create a new user instance. * Create a new user instance.
* *
* @param string $socialSecurityNumber * @param string|null $socialSecurityNumber
* @return \Tests\Mocks\User|\Illuminate\Database\Eloquent\Model * @return \Tests\Mocks\User|\Illuminate\Database\Eloquent\Model
*/ */
protected function user(string $socialSecurityNumber): User protected function user(?string $socialSecurityNumber): User
{ {
return User::query()->create([ return User::query()->create([
'social_security_number' => $socialSecurityNumber, 'social_security_number' => $socialSecurityNumber,

View File

@ -27,12 +27,12 @@ class EncryptionTest extends TestCase
'social_security_number' => '123-456-789', 'social_security_number' => '123-456-789',
]); ]);
$this->assertSame( static::assertSame(
'123-456-789', '123-456-789',
$user->social_security_number $user->social_security_number
); );
$this->assertNotEmpty( static::assertNotEmpty(
$user->social_security_number_index $user->social_security_number_index
); );
} }
@ -41,12 +41,12 @@ class EncryptionTest extends TestCase
{ {
$user = $this->user('123-456-789'); $user = $this->user('123-456-789');
$this->assertNotSame( static::assertNotSame(
'123-456-789', '123-456-789',
$user->getRawOriginal('social_security_number') $user->getRawOriginal('social_security_number')
); );
$this->assertNotEmpty( static::assertNotEmpty(
$user->social_security_number_index $user->social_security_number_index
); );
@ -78,12 +78,12 @@ class EncryptionTest extends TestCase
]) ])
->save(); ->save();
$this->assertNotSame( static::assertNotSame(
'123-456-789', '123-456-789',
$user->getRawOriginal('social_security_number') $user->getRawOriginal('social_security_number')
); );
$this->assertNotEmpty( static::assertNotEmpty(
$user->getAttribute('custom_index') $user->getAttribute('custom_index')
); );
@ -100,9 +100,29 @@ class EncryptionTest extends TestCase
{ {
$user = $this->user('123-456-789'); $user = $this->user('123-456-789');
$this->assertSame( static::assertSame(
'123-456-789', '123-456-789',
$user->getAttribute('social_security_number') $user->getAttribute('social_security_number')
); );
} }
public function testAttributesCanBeMadeNull(): void
{
$user = $this->user('123-456-789');
static::assertSame(
'123-456-789',
$user->social_security_number
);
$user->social_security_number = null;
static::assertNull(
$user->social_security_number
);
static::assertNull(
$user->social_security_number_index
);
}
} }

View File

@ -32,8 +32,8 @@ class QueryTest extends TestCase
->get() ->get()
->modelKeys(); ->modelKeys();
$this->assertContains($userOne->id, $keys); static::assertContains($userOne->id, $keys);
$this->assertNotContains($userTwo->id, $keys); static::assertNotContains($userTwo->id, $keys);
// Assert success using provided index. // Assert success using provided index.
/** @var \Illuminate\Database\Eloquent\Collection $keys */ /** @var \Illuminate\Database\Eloquent\Collection $keys */
@ -44,8 +44,8 @@ class QueryTest extends TestCase
->get() ->get()
->modelKeys(); ->modelKeys();
$this->assertContains($userOne->id, $keys); static::assertContains($userOne->id, $keys);
$this->assertNotContains($userTwo->id, $keys); static::assertNotContains($userTwo->id, $keys);
// Assert undefined index exception. // Assert undefined index exception.
$this->expectException(Exception::class); $this->expectException(Exception::class);
@ -57,6 +57,22 @@ class QueryTest extends TestCase
->get(); ->get();
} }
public function testCanQueryNullableAttributes(): void
{
$userOne = $this->user('123-456-789');
$userTwo = $this->user(null);
// Assert success.
/** @var \Illuminate\Database\Eloquent\Collection $keys */
$keys = User::query()
->whereEncrypted('social_security_number', '=', '123-456-789')
->get()
->modelKeys();
static::assertContains($userOne->id, $keys);
static::assertNotContains($userTwo->id, $keys);
}
public function testCanQueryEncryptedAttributeWithOrWhereClause(): void public function testCanQueryEncryptedAttributeWithOrWhereClause(): void
{ {
$userOne = $this->user('123-456-789'); $userOne = $this->user('123-456-789');
@ -71,9 +87,9 @@ class QueryTest extends TestCase
->get() ->get()
->modelKeys(); ->modelKeys();
$this->assertContains($userOne->id, $keys); static::assertContains($userOne->id, $keys);
$this->assertNotContains($userTwo->id, $keys); static::assertNotContains($userTwo->id, $keys);
$this->assertContains($userThree->id, $keys); static::assertContains($userThree->id, $keys);
// Assert success using provided index. // Assert success using provided index.
/** @var \Illuminate\Database\Eloquent\Collection $keys */ /** @var \Illuminate\Database\Eloquent\Collection $keys */
@ -85,9 +101,9 @@ class QueryTest extends TestCase
->get() ->get()
->modelKeys(); ->modelKeys();
$this->assertContains($userOne->id, $keys); static::assertContains($userOne->id, $keys);
$this->assertNotContains($userTwo->id, $keys); static::assertNotContains($userTwo->id, $keys);
$this->assertContains($userThree->id, $keys); static::assertContains($userThree->id, $keys);
// Assert undefined index exception. // Assert undefined index exception.
$this->expectException(Exception::class); $this->expectException(Exception::class);
@ -98,4 +114,118 @@ class QueryTest extends TestCase
]) ])
->get(); ->get();
} }
public function testCanQueryEncryptedAttributeWithWhereInClause(): void
{
$userOne = $this->user('123-456-789');
$userTwo = $this->user('789-456-123');
$userThree = $this->user('456-789-123');
// Assert success.
/** @var \Illuminate\Database\Eloquent\Collection $keys */
$keys = User::query()
->whereInEncrypted(
'social_security_number',
[
'123-456-789',
'789-456-123',
]
)
->get()
->modelKeys();
static::assertContains($userOne->id, $keys);
static::assertContains($userTwo->id, $keys);
static::assertNotContains($userThree->id, $keys);
// Assert success using provided index.
/** @var \Illuminate\Database\Eloquent\Collection $keys */
$keys = User::query()
->whereInEncrypted(
'social_security_number',
[
'123-456-789',
'789-456-123',
],
['social_security_number_index']
)
->get()
->modelKeys();
static::assertContains($userOne->id, $keys);
static::assertContains($userTwo->id, $keys);
static::assertNotContains($userThree->id, $keys);
$keys = User::query()
->whereInEncrypted(
'social_security_number',
['789-456-123'],
['non_existing_index']
)
->get()
->modelKeys();
static::assertEmpty($keys);
}
public function testCanQueryEncryptedAttributeWithOrWhereInClause(): void
{
$userOne = $this->user('123-456-789');
$userTwo = $this->user('789-456-123');
$userThree = $this->user('456-789-123');
// Assert success.
/** @var \Illuminate\Database\Eloquent\Collection $keys */
$keys = User::query()
->whereInEncrypted(
'social_security_number',
['123-456-789']
)
->orWhereInEncrypted(
'social_security_number',
['789-456-123']
)
->get()
->modelKeys();
static::assertContains($userOne->id, $keys);
static::assertContains($userTwo->id, $keys);
static::assertNotContains($userThree->id, $keys);
// Assert success using provided index.
/** @var \Illuminate\Database\Eloquent\Collection $keys */
$keys = User::query()
->whereInEncrypted(
'social_security_number',
['123-456-789'],
['social_security_number_index']
)
->orWhereInEncrypted(
'social_security_number',
['789-456-123'],
['social_security_number_index']
)
->get()
->modelKeys();
static::assertContains($userOne->id, $keys);
static::assertContains($userTwo->id, $keys);
static::assertNotContains($userThree->id, $keys);
$keys = User::query()
->whereInEncrypted(
'social_security_number',
['789-456-123'],
['non_existing_index']
)
->orWhereInEncrypted(
'social_security_number',
['789-456-123'],
['non_existing_index']
)
->get()
->modelKeys();
static::assertEmpty($keys);
}
} }