Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion MIGRATING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Migrating swisnl/laravel-encrypted-data

## To Laravel Encrypted Casting
## To 3.x from 2.x

### Laravel Encrypted Casting
The main difference between this package and [Laravel Encrypted Casting](https://laravel.com/docs/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:

1. Make sure you're running on Laravel 12.20 or higher.
Expand Down Expand Up @@ -54,3 +56,29 @@ php artisan encrypted-data:re-encrypt:models --quietly --no-touch
```
N.B. Use `--help` to see all available options and modify as needed!
8. Remove our custom model encrypter from your `AppServiceProvider` (step 6).

### Filesystem
If you're using the encrypted filesystem, make sure to update your configuration in `config/filesystems.php` as shown below:

**Before:**
```php
'disks' => [
'local' => [
'driver' => 'local-encrypted',
'root' => storage_path('app'),
],
],
```

**After:**
```php
'disks' => [
'local' => [
'driver' => 'encrypted',
'disk' => [
'driver' => 'local',
'root' => storage_path('app'),
],
],
],
```
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ composer require swisnl/laravel-encrypted-data
> Older versions of this package needed a custom model class to encrypt data. This is now replaced with custom casts. Please see [MIGRATING](MIGRATING.md) for a step-by-step guide on how to migrate.
>

You can use the Eloquent casts provided by this package and everything will be encrypted/decrypted under the hood!
You can use the Eloquent casts provided by this package just like any other cast. Encryption/decryption is handled automatically behind the scenes.

#### Boolean

Expand All @@ -51,18 +51,37 @@ protected $casts = [

### Filesystem

Configure the storage driver in `config/filesystems.php`.
This package provides a filesystem driver named `encrypted`, which transparently wraps another disk. You can continue using Laravel's standard storage methods and encryption/decryption is handled automatically behind the scenes.

To configure the `encrypted` driver, update `config/filesystems.php` with either a full inline disk configuration:

```php
'disks' => [
'local' => [
'driver' => 'local-encrypted',
'root' => storage_path('app'),
'driver' => 'encrypted',
'disk' => [
'driver' => 'local',
'root' => storage_path('app'),
],
],
],
```

You can now simply use the storage methods as usual and everything will be encrypted/decrypted under the hood!
Or reference an existing disk by name:

```php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],

'local-encrypted' => [
'driver' => 'encrypted',
'disk' => 'local',
],
],
```

### Commands

Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"league/flysystem-ftp": "^3.0",
"league/flysystem-path-prefixing": "^3.0",
"orchestra/testbench": "^10.0",
"phpunit/phpunit": "^11.5",
"symfony/console": "^7.3.3"
Expand Down
7 changes: 4 additions & 3 deletions src/Commands/ReEncryptFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Swis\Flysystem\Encrypted\EncryptedFilesystemAdapter;
use Swis\Laravel\Encrypted\EncryptedFilesystemAdapter;
use Symfony\Component\Console\Output\OutputInterface;

class ReEncryptFiles extends Command
Expand Down Expand Up @@ -75,6 +75,7 @@ protected function reEncryptFiles(string $disk): Collection
$directories->push('');
}

/** @var \Swis\Laravel\Encrypted\EncryptedFilesystemAdapter $filesystem */
$filesystem = Storage::disk($disk);

return $directories
Expand All @@ -93,7 +94,7 @@ protected function reEncryptFiles(string $disk): Collection
->unique()
->each(function (string $file) use ($filesystem) {
$this->line($file, verbosity: OutputInterface::VERBOSITY_VERBOSE);
$filesystem->put($file, $filesystem->get($file));
$filesystem->reEncrypt($file);
});
}

Expand Down Expand Up @@ -133,6 +134,6 @@ protected function disksCanBeReEncrypted(Collection $disks): bool
*/
protected function diskCanBeReEncrypted(string $disk): bool
{
return rescue(static fn (): bool => Storage::disk($disk)->getAdapter() instanceof EncryptedFilesystemAdapter, false, false);
return rescue(static fn (): bool => Storage::disk($disk) instanceof EncryptedFilesystemAdapter, false, false);
}
}
52 changes: 17 additions & 35 deletions src/EncryptedDataServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@
namespace Swis\Laravel\Encrypted;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\Visibility;
use Swis\Flysystem\Encrypted\EncryptedFilesystemAdapter;
use Swis\Flysystem\Encrypted\EncryptedFilesystemAdapter as EncryptedAdapter;
use Swis\Laravel\Encrypted\Commands\ReEncryptFiles;
use Swis\Laravel\Encrypted\Commands\ReEncryptModels;

Expand Down Expand Up @@ -42,40 +36,28 @@ public function boot(): void
protected function setupStorageDriver(): void
{
Storage::extend(
'local-encrypted',
function (Application $app, array $config) {
$visibility = PortableVisibilityConverter::fromArray(
$config['permissions'] ?? [],
$config['directory_visibility'] ?? $config['visibility'] ?? Visibility::PRIVATE
'encrypted',
(function (Application $app, array $config) {
/* @var \Illuminate\Filesystem\FilesystemManager $this */
if (empty($config['disk'])) {
throw new \InvalidArgumentException('Encrypted disk is missing "disk" configuration option.');
}

$parent = $this->build(
is_string($config['disk']) ? $this->getConfig($config['disk']) : $config['disk']
);

$links = ($config['links'] ?? null) === 'skip'
? LocalFilesystemAdapter::SKIP_LINKS
: LocalFilesystemAdapter::DISALLOW_LINKS;

$adapter = new EncryptedFilesystemAdapter(
new LocalFilesystemAdapter(
$config['root'],
$visibility,
$config['lock'] ?? LOCK_EX,
$links
),
$encryptedAdapter = new EncryptedAdapter(
$parent->getAdapter(),
$app->make('encrypted-data.encrypter')
);

$driver = new Filesystem(
$adapter,
Arr::only($config, [
'directory_visibility',
'disable_asserts',
'temporary_url',
'url',
'visibility',
])
return new EncryptedFilesystemAdapter(
$this->createFlysystem($encryptedAdapter, $parent->getConfig()),
$parent->getAdapter(),
$parent->getConfig()
);

return new FilesystemAdapter($driver, $adapter, $config);
}
})->bindTo(Storage::getFacadeRoot(), Storage::getFacadeRoot())
);
}
}
16 changes: 16 additions & 0 deletions src/EncryptedFilesystemAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Swis\Laravel\Encrypted;

use Illuminate\Filesystem\FilesystemAdapter;

class EncryptedFilesystemAdapter extends FilesystemAdapter
{
/**
* Re-encrypt the contents of a file.
*/
public function reEncrypt(string $path): bool
{
return (bool) $this->put($path, $this->get($path));
}
}
4 changes: 2 additions & 2 deletions tests/Feature/ReEncryptFilesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ protected function hasLocalDisk($app): void
protected function hasEncryptedDisk($app): void
{
$app['config']->set('filesystems.default', 'local');
$app['config']->set('filesystems.disks.local', ['driver' => 'local-encrypted', 'root' => $this->diskRoot]);
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => ['driver' => 'local', 'root' => $this->diskRoot]]);

$this->filesystem->ensureDirectoryExists($this->diskRoot);
}

protected function hasExtraEncryptedDisk($app): void
{
$diskRoot = dirname($this->diskRoot).'/extra';
$app['config']->set('filesystems.disks.extra', ['driver' => 'local-encrypted', 'root' => $diskRoot]);
$app['config']->set('filesystems.disks.extra', ['driver' => 'encrypted', 'disk' => ['driver' => 'local', 'root' => $diskRoot]]);

$this->filesystem->ensureDirectoryExists($diskRoot);
}
Expand Down
71 changes: 67 additions & 4 deletions tests/Unit/FilesystemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,87 @@
namespace Swis\Laravel\Encrypted\Tests\Unit;

use Illuminate\Support\Facades\Storage;
use League\Flysystem\Ftp\FtpAdapter;
use Orchestra\Testbench\Attributes\DefineEnvironment;
use PHPUnit\Framework\Attributes\Test;
use Swis\Laravel\Encrypted\Tests\TestCase;

final class FilesystemTest extends TestCase
{
protected function usesEncryptedDisk($app): void
protected function hasEncryptedInlineDisk($app): void
{
$app['config']->set('filesystems.default', 'local');
$app['config']->set('filesystems.disks.local', ['driver' => 'local-encrypted', 'root' => dirname(__DIR__).'/_files/']);
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => ['driver' => 'local', 'root' => dirname(__DIR__).'/_files/']]);
}

protected function hasEncryptedReferencedDisk($app): void
{
$app['config']->set('filesystems.default', 'local');
$app['config']->set('filesystems.disks.other', ['driver' => 'local', 'root' => dirname(__DIR__).'/_files/']);
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => 'other']);
}

protected function hasEncryptedFtpDisk($app): void
{
$app['config']->set('filesystems.default', 'ftp');
$app['config']->set('filesystems.disks.ftp', ['driver' => 'encrypted', 'disk' => ['driver' => 'ftp', 'host' => 'localhost']]);
}

protected function hasEncryptedDiskWithPrefix($app): void
{
$app['config']->set('filesystems.default', 'local');
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => ['driver' => 'local', 'root' => dirname(__DIR__).'/_files/', 'prefix' => 'prefix']]);
}

protected function hasIncorrectEncryptedDisk($app): void
{
$app['config']->set('filesystems.default', 'local');
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted']);
}

#[Test]
#[DefineEnvironment('hasEncryptedInlineDisk')]
public function itRegistersTheFilesystemDriverWithInlineDisk(): void
{
$contents = Storage::get('read.txt');

$this->assertSame('YSvdOxSZ8pyTdDWeN8qI', $contents);
}

#[Test]
#[DefineEnvironment('usesEncryptedDisk')]
public function itRegistersTheFilesystemDriver(): void
#[DefineEnvironment('hasEncryptedReferencedDisk')]
public function itRegistersTheFilesystemDriverWithReferencedDisk(): void
{
$contents = Storage::get('read.txt');

$this->assertSame('YSvdOxSZ8pyTdDWeN8qI', $contents);
}

#[Test]
#[DefineEnvironment('hasEncryptedFtpDisk')]
public function itRegistersTheFilesystemDriverWithFtpDisk(): void
{
$filesystem = Storage::disk();

$this->assertInstanceOf(FtpAdapter::class, $filesystem->getAdapter());
}

#[Test]
#[DefineEnvironment('hasEncryptedDiskWithPrefix')]
public function itRegistersTheFilesystemDriverWithPrefixedDisk(): void
{
$contents = Storage::get('read.txt');

$this->assertSame('hi7OJgUQlfk00nd3jmM1', $contents);
}

#[Test]
#[DefineEnvironment('hasIncorrectEncryptedDisk')]
public function itFailsWhenDiskIsMissing(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Encrypted disk is missing "disk" configuration option.');

Storage::get('read.txt');
}
}
1 change: 1 addition & 0 deletions tests/_files/prefix/read.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJpdiI6IllsYmJDQTdlaXBiaVc4K1NXQ0w4M0E9PSIsInZhbHVlIjoiS2k2YVJqcHZ5eEh5MEFoTmE2bXJ1TXlFeTE1dE1jb0psN2tTWU9LWW9VZz0iLCJtYWMiOiJlNWJkYzAwNTJkZjY0Y2M4MTMxNzg1OTcxNzU5YTM0MmI1YWIxNWZiZjA1OGRmZjQwOTVkNWY1OTNlMzdlZmEwIiwidGFnIjoiIn0=