Compare commits
10 Commits
cff6336828
...
9193ff649a
| Author | SHA1 | Date |
|---|---|---|
|
|
9193ff649a | |
|
|
931673fd29 | |
|
|
921f1df2ad | |
|
|
5fdadf1b46 | |
|
|
250e4c7352 | |
|
|
f91b31717d | |
|
|
13bfb5e4ee | |
|
|
278719a552 | |
|
|
ddf24dd78f | |
|
|
dfce155a77 |
|
|
@ -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",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
54
phpunit.xml
54
phpunit.xml
|
|
@ -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>
|
||||||
|
|
|
||||||
41
readme.md
41
readme.md
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue