Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add read only support #150

Merged
merged 1 commit into from
Oct 31, 2023
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"league/flysystem-ftp": "^3.1",
"league/flysystem-google-cloud-storage": "^3.1",
"league/flysystem-memory": "^3.1",
"league/flysystem-read-only": "^3.15",
"league/flysystem-sftp-v3": "^3.1",
"symfony/dotenv": "^5.4|^6.0",
"symfony/framework-bundle": "^5.4|^6.0",
Expand Down
34 changes: 33 additions & 1 deletion docs/1-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,13 @@ it gives you to swap the actual implementation during tests.
More specifically, it can be useful to swap from a persisted storage to a memory one during
tests, both to ensure the state is reset between tests and to increase tests speed.

To achieve this, you can overwrite your storages in the test environment:
To achieve this, you need to install the memory provider:

```
composer require league/flysystem-memory
```

Then, you can overwrite your storages in the test environment:

```yaml
# config/packages/flysystem.yaml
Expand All @@ -162,6 +168,32 @@ flysystem:
This configuration will swap every reference to the `users.storage` service (or to the
`FilesystemOperator $usersStorage` typehint) from a local adapter to a memory one during tests.

## Using read only to disallow any write operations

In some context, it can be useful to protect any write operations on your storages service.

To achieve this, you need to install the read-only package :

```
composer require league/flysystem-read-only
```

And then, you can configure your storage with the `readonly` options.

```yaml
# config/packages/flysystem.yaml

flysystem:
storages:
users.storage:
adapter: 'local'
options:
directory: '%kernel.project_dir%/storage/users'
readonly: true
```

With this configuration, any write operation will throw a suitable exception.

## Next

[Cloud storage providers](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/2-cloud-storage-providers.md)
20 changes: 14 additions & 6 deletions docs/B-configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ flysystem:
prefix: 'optional/path/prefix'

users2.storage:
adapter: 'azure'
options:
client: 'azure_client_service'
container: 'container_name'
prefix: 'optional/path/prefix'
adapter: 'azure'
options:
client: 'azure_client_service'
container: 'container_name'
prefix: 'optional/path/prefix'

users3.storage:
adapter: 'ftp'
Expand Down Expand Up @@ -78,5 +78,13 @@ flysystem:
source: 'flysystem_storage_service_to_use'

users10.storage:
adapter: 'custom_adapter'
adapter: 'custom_adapter'

users11.storage:
adapter: 'local'
options:
directory: '/tmp/storage'
public_url_generator: 'flysystem_public_url_generator_service_to_use'
temporary_url_generator: 'flysystem_temporary_url_generator_service_to_use'
read_only: true
```
1 change: 1 addition & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->scalarNode('public_url_generator')->defaultNull()->end()
->scalarNode('temporary_url_generator')->defaultNull()->end()
->booleanNode('read_only')->defaultFalse()->end()
->end()
->end()
->defaultValue([])
Expand Down
30 changes: 27 additions & 3 deletions src/DependencyInjection/FlysystemExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
use League\Flysystem\FilesystemOperator;
use League\Flysystem\FilesystemReader;
use League\Flysystem\FilesystemWriter;
use League\Flysystem\ReadOnly\ReadOnlyFilesystemAdapter;
use League\FlysystemBundle\Adapter\AdapterDefinitionFactory;
use League\FlysystemBundle\Exception\MissingPackageException;
use League\FlysystemBundle\Lazy\LazyFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
Expand Down Expand Up @@ -63,16 +65,29 @@ private function createStoragesDefinitions(array $config, ContainerBuilder $cont
// Create adapter definition
if ($adapter = $definitionFactory->createDefinition($storageConfig['adapter'], $storageConfig['options'], $storageConfig['directory_visibility'] ?? null)) {
// Native adapter
$container->setDefinition('flysystem.adapter.'.$storageName, $adapter)->setPublic(false);
$container->setDefinition($id = 'flysystem.adapter.'.$storageName, $adapter)->setPublic(false);
} else {
// Custom adapter
$container->setAlias('flysystem.adapter.'.$storageName, $storageConfig['adapter'])->setPublic(false);
$container->setAlias($id = 'flysystem.adapter.'.$storageName, $storageConfig['adapter'])->setPublic(false);
}

// Create ReadOnly adapter
if ($storageConfig['read_only']) {
if (!class_exists(ReadOnlyFilesystemAdapter::class)) {
throw new MissingPackageException("Missing package, to use the readonly option, run:\n\ncomposer require league/flysystem-read-only");
}

$originalAdapterId = $id;
$container->setDefinition(
$id = $id.'.read_only',
$this->createReadOnlyAdapterDefinition(new Reference($originalAdapterId))
);
}

// Create storage definition
$container->setDefinition(
$storageName,
$this->createStorageDefinition($storageName, new Reference('flysystem.adapter.'.$storageName), $storageConfig)
$this->createStorageDefinition($storageName, new Reference($id), $storageConfig)
);

// Register named autowiring alias
Expand Down Expand Up @@ -122,4 +137,13 @@ private function createStorageDefinition(string $storageName, Reference $adapter

return $definition;
}

private function createReadOnlyAdapterDefinition(Reference $adapter): Definition
{
$definition = new Definition(ReadOnlyFilesystemAdapter::class);
$definition->setPublic(false);
$definition->setArgument(0, $adapter);

return $definition;
}
}
14 changes: 14 additions & 0 deletions tests/DependencyInjection/FlysystemExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Google\Cloud\Storage\Bucket;
use Google\Cloud\Storage\StorageClient;
use League\Flysystem\FilesystemOperator;
use League\Flysystem\UnableToWriteFile;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Dotenv\Dotenv;
Expand Down Expand Up @@ -104,6 +105,19 @@ public function testUrlGenerators()
self::assertSame('https://example.org/temporary/test1.txt?expiresAt=1670846026', $fs->temporaryUrl('test1.txt', new \DateTimeImmutable('@1670846026')));
}

public function testReadOnly()
{
$kernel = $this->createFysystemKernel();
$container = $kernel->getContainer()->get('test.service_container');

$fs = $container->get('flysystem.test.fs_read_only');

$this->expectException(UnableToWriteFile::class);
$this->expectExceptionMessage('Unable to write file at location: path/to/file. This is a readonly adapter.');

$fs->write('/path/to/file', 'Unable to write in read only');
}

private function createFysystemKernel(): FlysystemAppKernel
{
(new Dotenv())->populate([
Expand Down
6 changes: 6 additions & 0 deletions tests/Kernel/config/flysystem.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,9 @@ flysystem:
directory: '/tmp/storage'
public_url_generator: 'flysystem.test.public_url_generator'
temporary_url_generator: 'flysystem.test.temporary_url_generator'

fs_read_only:
adapter: 'local'
options:
directory: '/tmp/storage'
read_only: true
1 change: 1 addition & 0 deletions tests/Kernel/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ services:
flysystem.test.fs_public_url: { alias: 'fs_public_url' }
flysystem.test.fs_public_urls: { alias: 'fs_public_urls' }
flysystem.test.fs_url_generator: { alias: 'fs_url_generator' }
flysystem.test.fs_read_only: { alias: 'fs_read_only' }