Skip to content

Commit 8799deb

Browse files
authored
Merge pull request #11 from swisnl/feature/decorate-disk
Encrypted disk now decorates another disk
2 parents 3c7f8c5 + f74048f commit 8799deb

9 files changed

Lines changed: 162 additions & 50 deletions

File tree

MIGRATING.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Migrating swisnl/laravel-encrypted-data
22

3-
## To Laravel Encrypted Casting
3+
## To 3.x from 2.x
4+
5+
### Laravel Encrypted Casting
46
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:
57

68
1. Make sure you're running on Laravel 12.20 or higher.
@@ -54,3 +56,29 @@ php artisan encrypted-data:re-encrypt:models --quietly --no-touch
5456
```
5557
N.B. Use `--help` to see all available options and modify as needed!
5658
8. Remove our custom model encrypter from your `AppServiceProvider` (step 6).
59+
60+
### Filesystem
61+
If you're using the encrypted filesystem, make sure to update your configuration in `config/filesystems.php` as shown below:
62+
63+
**Before:**
64+
```php
65+
'disks' => [
66+
'local' => [
67+
'driver' => 'local-encrypted',
68+
'root' => storage_path('app'),
69+
],
70+
],
71+
```
72+
73+
**After:**
74+
```php
75+
'disks' => [
76+
'local' => [
77+
'driver' => 'encrypted',
78+
'disk' => [
79+
'driver' => 'local',
80+
'root' => storage_path('app'),
81+
],
82+
],
83+
],
84+
```

README.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ composer require swisnl/laravel-encrypted-data
2727
> 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.
2828
>
2929
30-
You can use the Eloquent casts provided by this package and everything will be encrypted/decrypted under the hood!
30+
You can use the Eloquent casts provided by this package just like any other cast. Encryption/decryption is handled automatically behind the scenes.
3131

3232
#### Boolean
3333

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

5252
### Filesystem
5353

54-
Configure the storage driver in `config/filesystems.php`.
54+
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.
55+
56+
To configure the `encrypted` driver, update `config/filesystems.php` with either a full inline disk configuration:
5557

5658
```php
5759
'disks' => [
5860
'local' => [
59-
'driver' => 'local-encrypted',
60-
'root' => storage_path('app'),
61+
'driver' => 'encrypted',
62+
'disk' => [
63+
'driver' => 'local',
64+
'root' => storage_path('app'),
65+
],
6166
],
6267
],
6368
```
6469

65-
You can now simply use the storage methods as usual and everything will be encrypted/decrypted under the hood!
70+
Or reference an existing disk by name:
71+
72+
```php
73+
'disks' => [
74+
'local' => [
75+
'driver' => 'local',
76+
'root' => storage_path('app'),
77+
],
78+
79+
'local-encrypted' => [
80+
'driver' => 'encrypted',
81+
'disk' => 'local',
82+
],
83+
],
84+
```
6685

6786
### Commands
6887

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
},
2727
"require-dev": {
2828
"friendsofphp/php-cs-fixer": "^3.0",
29+
"league/flysystem-ftp": "^3.0",
30+
"league/flysystem-path-prefixing": "^3.0",
2931
"orchestra/testbench": "^10.0",
3032
"phpunit/phpunit": "^11.5",
3133
"symfony/console": "^7.3.3"

src/Commands/ReEncryptFiles.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Illuminate\Console\Command;
88
use Illuminate\Support\Collection;
99
use Illuminate\Support\Facades\Storage;
10-
use Swis\Flysystem\Encrypted\EncryptedFilesystemAdapter;
10+
use Swis\Laravel\Encrypted\EncryptedFilesystemAdapter;
1111
use Symfony\Component\Console\Output\OutputInterface;
1212

1313
class ReEncryptFiles extends Command
@@ -75,6 +75,7 @@ protected function reEncryptFiles(string $disk): Collection
7575
$directories->push('');
7676
}
7777

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

8081
return $directories
@@ -93,7 +94,7 @@ protected function reEncryptFiles(string $disk): Collection
9394
->unique()
9495
->each(function (string $file) use ($filesystem) {
9596
$this->line($file, verbosity: OutputInterface::VERBOSITY_VERBOSE);
96-
$filesystem->put($file, $filesystem->get($file));
97+
$filesystem->reEncrypt($file);
9798
});
9899
}
99100

@@ -133,6 +134,6 @@ protected function disksCanBeReEncrypted(Collection $disks): bool
133134
*/
134135
protected function diskCanBeReEncrypted(string $disk): bool
135136
{
136-
return rescue(static fn (): bool => Storage::disk($disk)->getAdapter() instanceof EncryptedFilesystemAdapter, false, false);
137+
return rescue(static fn (): bool => Storage::disk($disk) instanceof EncryptedFilesystemAdapter, false, false);
137138
}
138139
}

src/EncryptedDataServiceProvider.php

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,9 @@
33
namespace Swis\Laravel\Encrypted;
44

55
use Illuminate\Contracts\Foundation\Application;
6-
use Illuminate\Filesystem\FilesystemAdapter;
7-
use Illuminate\Support\Arr;
86
use Illuminate\Support\Facades\Storage;
97
use Illuminate\Support\ServiceProvider;
10-
use League\Flysystem\Filesystem;
11-
use League\Flysystem\Local\LocalFilesystemAdapter;
12-
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
13-
use League\Flysystem\Visibility;
14-
use Swis\Flysystem\Encrypted\EncryptedFilesystemAdapter;
8+
use Swis\Flysystem\Encrypted\EncryptedFilesystemAdapter as EncryptedAdapter;
159
use Swis\Laravel\Encrypted\Commands\ReEncryptFiles;
1610
use Swis\Laravel\Encrypted\Commands\ReEncryptModels;
1711

@@ -42,40 +36,28 @@ public function boot(): void
4236
protected function setupStorageDriver(): void
4337
{
4438
Storage::extend(
45-
'local-encrypted',
46-
function (Application $app, array $config) {
47-
$visibility = PortableVisibilityConverter::fromArray(
48-
$config['permissions'] ?? [],
49-
$config['directory_visibility'] ?? $config['visibility'] ?? Visibility::PRIVATE
39+
'encrypted',
40+
(function (Application $app, array $config) {
41+
/* @var \Illuminate\Filesystem\FilesystemManager $this */
42+
if (empty($config['disk'])) {
43+
throw new \InvalidArgumentException('Encrypted disk is missing "disk" configuration option.');
44+
}
45+
46+
$parent = $this->build(
47+
is_string($config['disk']) ? $this->getConfig($config['disk']) : $config['disk']
5048
);
5149

52-
$links = ($config['links'] ?? null) === 'skip'
53-
? LocalFilesystemAdapter::SKIP_LINKS
54-
: LocalFilesystemAdapter::DISALLOW_LINKS;
55-
56-
$adapter = new EncryptedFilesystemAdapter(
57-
new LocalFilesystemAdapter(
58-
$config['root'],
59-
$visibility,
60-
$config['lock'] ?? LOCK_EX,
61-
$links
62-
),
50+
$encryptedAdapter = new EncryptedAdapter(
51+
$parent->getAdapter(),
6352
$app->make('encrypted-data.encrypter')
6453
);
6554

66-
$driver = new Filesystem(
67-
$adapter,
68-
Arr::only($config, [
69-
'directory_visibility',
70-
'disable_asserts',
71-
'temporary_url',
72-
'url',
73-
'visibility',
74-
])
55+
return new EncryptedFilesystemAdapter(
56+
$this->createFlysystem($encryptedAdapter, $parent->getConfig()),
57+
$parent->getAdapter(),
58+
$parent->getConfig()
7559
);
76-
77-
return new FilesystemAdapter($driver, $adapter, $config);
78-
}
60+
})->bindTo(Storage::getFacadeRoot(), Storage::getFacadeRoot())
7961
);
8062
}
8163
}

src/EncryptedFilesystemAdapter.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Swis\Laravel\Encrypted;
4+
5+
use Illuminate\Filesystem\FilesystemAdapter;
6+
7+
class EncryptedFilesystemAdapter extends FilesystemAdapter
8+
{
9+
/**
10+
* Re-encrypt the contents of a file.
11+
*/
12+
public function reEncrypt(string $path): bool
13+
{
14+
return (bool) $this->put($path, $this->get($path));
15+
}
16+
}

tests/Feature/ReEncryptFilesTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ protected function hasLocalDisk($app): void
5252
protected function hasEncryptedDisk($app): void
5353
{
5454
$app['config']->set('filesystems.default', 'local');
55-
$app['config']->set('filesystems.disks.local', ['driver' => 'local-encrypted', 'root' => $this->diskRoot]);
55+
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => ['driver' => 'local', 'root' => $this->diskRoot]]);
5656

5757
$this->filesystem->ensureDirectoryExists($this->diskRoot);
5858
}
5959

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

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

tests/Unit/FilesystemTest.php

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,87 @@
33
namespace Swis\Laravel\Encrypted\Tests\Unit;
44

55
use Illuminate\Support\Facades\Storage;
6+
use League\Flysystem\Ftp\FtpAdapter;
67
use Orchestra\Testbench\Attributes\DefineEnvironment;
78
use PHPUnit\Framework\Attributes\Test;
89
use Swis\Laravel\Encrypted\Tests\TestCase;
910

1011
final class FilesystemTest extends TestCase
1112
{
12-
protected function usesEncryptedDisk($app): void
13+
protected function hasEncryptedInlineDisk($app): void
1314
{
1415
$app['config']->set('filesystems.default', 'local');
15-
$app['config']->set('filesystems.disks.local', ['driver' => 'local-encrypted', 'root' => dirname(__DIR__).'/_files/']);
16+
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => ['driver' => 'local', 'root' => dirname(__DIR__).'/_files/']]);
17+
}
18+
19+
protected function hasEncryptedReferencedDisk($app): void
20+
{
21+
$app['config']->set('filesystems.default', 'local');
22+
$app['config']->set('filesystems.disks.other', ['driver' => 'local', 'root' => dirname(__DIR__).'/_files/']);
23+
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => 'other']);
24+
}
25+
26+
protected function hasEncryptedFtpDisk($app): void
27+
{
28+
$app['config']->set('filesystems.default', 'ftp');
29+
$app['config']->set('filesystems.disks.ftp', ['driver' => 'encrypted', 'disk' => ['driver' => 'ftp', 'host' => 'localhost']]);
30+
}
31+
32+
protected function hasEncryptedDiskWithPrefix($app): void
33+
{
34+
$app['config']->set('filesystems.default', 'local');
35+
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted', 'disk' => ['driver' => 'local', 'root' => dirname(__DIR__).'/_files/', 'prefix' => 'prefix']]);
36+
}
37+
38+
protected function hasIncorrectEncryptedDisk($app): void
39+
{
40+
$app['config']->set('filesystems.default', 'local');
41+
$app['config']->set('filesystems.disks.local', ['driver' => 'encrypted']);
42+
}
43+
44+
#[Test]
45+
#[DefineEnvironment('hasEncryptedInlineDisk')]
46+
public function itRegistersTheFilesystemDriverWithInlineDisk(): void
47+
{
48+
$contents = Storage::get('read.txt');
49+
50+
$this->assertSame('YSvdOxSZ8pyTdDWeN8qI', $contents);
1651
}
1752

1853
#[Test]
19-
#[DefineEnvironment('usesEncryptedDisk')]
20-
public function itRegistersTheFilesystemDriver(): void
54+
#[DefineEnvironment('hasEncryptedReferencedDisk')]
55+
public function itRegistersTheFilesystemDriverWithReferencedDisk(): void
2156
{
2257
$contents = Storage::get('read.txt');
2358

2459
$this->assertSame('YSvdOxSZ8pyTdDWeN8qI', $contents);
2560
}
61+
62+
#[Test]
63+
#[DefineEnvironment('hasEncryptedFtpDisk')]
64+
public function itRegistersTheFilesystemDriverWithFtpDisk(): void
65+
{
66+
$filesystem = Storage::disk();
67+
68+
$this->assertInstanceOf(FtpAdapter::class, $filesystem->getAdapter());
69+
}
70+
71+
#[Test]
72+
#[DefineEnvironment('hasEncryptedDiskWithPrefix')]
73+
public function itRegistersTheFilesystemDriverWithPrefixedDisk(): void
74+
{
75+
$contents = Storage::get('read.txt');
76+
77+
$this->assertSame('hi7OJgUQlfk00nd3jmM1', $contents);
78+
}
79+
80+
#[Test]
81+
#[DefineEnvironment('hasIncorrectEncryptedDisk')]
82+
public function itFailsWhenDiskIsMissing(): void
83+
{
84+
$this->expectException(\InvalidArgumentException::class);
85+
$this->expectExceptionMessage('Encrypted disk is missing "disk" configuration option.');
86+
87+
Storage::get('read.txt');
88+
}
2689
}

tests/_files/prefix/read.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJpdiI6IllsYmJDQTdlaXBiaVc4K1NXQ0w4M0E9PSIsInZhbHVlIjoiS2k2YVJqcHZ5eEh5MEFoTmE2bXJ1TXlFeTE1dE1jb0psN2tTWU9LWW9VZz0iLCJtYWMiOiJlNWJkYzAwNTJkZjY0Y2M4MTMxNzg1OTcxNzU5YTM0MmI1YWIxNWZiZjA1OGRmZjQwOTVkNWY1OTNlMzdlZmEwIiwidGFnIjoiIn0=

0 commit comments

Comments
 (0)