Skip to content

Commit d7e0477

Browse files
authored
Merge pull request #669 from code16/download-allowed-disks
Implement config downloads.allowed_disks
2 parents 4f3ac83 + f53d76b commit d7e0477

File tree

5 files changed

+90
-3
lines changed

5 files changed

+90
-3
lines changed

docs/guide/form-fields/upload.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,23 @@ class SharpServiceProvider extends SharpAppServiceProvider
2323
}
2424
```
2525

26-
The fourth argument, `keepOriginalImageOnTransform`, is a boolean that defines if the original image should be kept when a transformation is applied on it (meaning that transformations are stored and applied on-the-fly: this is transparent when using Sharp’s [built-in way to handle uploads](../sharp-uploads.md). It can be overriden by each field (see below).
26+
The fourth argument, `keepOriginalImageOnTransform`, is a boolean that defines if the original image should be kept when a transformation is applied on it (meaning that transformations are stored and applied on-the-fly: this is transparent when using Sharp’s [built-in way to handle uploads](../sharp-uploads.md). It can be overridden by each field (see below).
27+
28+
Sharp allows admins to download all uploaded files directly from the Upload field UI. However, this capability may introduce security concerns, since Sharp can access any file on the server (although this is largely mitigated by Flysystem, which is used under the hood). You can control this behavior by specifying a list of allowed disks in the configuration:
29+
30+
```php
31+
class SharpServiceProvider extends SharpAppServiceProvider
32+
{
33+
protected function configureSharp(SharpConfigBuilder $config): void
34+
{
35+
$config
36+
->configureDownloads(
37+
allowedDisks: ['local', 'public'],
38+
)
39+
// ...
40+
}
41+
}
42+
```
2743

2844
## Field Configuration
2945

docs/guide/show-fields/file.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,28 @@ Sharp expects an array formatted like this:
2525
]
2626
```
2727

28-
If you are using Sharp solution for uploads, meaning the `SharpUploadModel` class [detailed here](../sharp-uploads.md), you can simply call the built-in transformer:
28+
If you are using Sharp’s solution for uploads, meaning the `SharpUploadModel` class [detailed here](../sharp-uploads.md), you can call the built-in transformer:
2929

3030
```php
3131
$this->setCustomTransformer('file', new SharpUploadModelFormAttributeTransformer());
3232
```
3333

34-
This transformer allows to act a bit on the thumbnail creation part, see its constructor for more details.
34+
This transformer allows acting a bit on the thumbnail creation part, see its constructor for more details.
35+
36+
## A note on security
37+
38+
Sharp allows admins to download all uploaded files directly from the File field UI. However, this capability may introduce security concerns, since Sharp can access any file on the server (although this is largely mitigated by Flysystem, which is used under the hood). You can control this behavior by specifying a list of allowed disks in the configuration:
39+
40+
```php
41+
class SharpServiceProvider extends SharpAppServiceProvider
42+
{
43+
protected function configureSharp(SharpConfigBuilder $config): void
44+
{
45+
$config
46+
->configureDownloads(
47+
allowedDisks: ['local', 'public'],
48+
)
49+
// ...
50+
}
51+
}
52+
```

src/Config/SharpConfigBuilder.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class SharpConfigBuilder
9393
'file_handling_queue' => 'default',
9494
'file_handling_queue_connection' => 'sync',
9595
],
96+
'downloads' => [
97+
'allowed_disks' => '*',
98+
],
9699
'search' => [
97100
'enabled' => false,
98101
],
@@ -320,6 +323,13 @@ public function configureUploadsThumbnailCreation(
320323
return $this;
321324
}
322325

326+
public function configureDownloads(array $allowedDisks): self
327+
{
328+
$this->config['downloads']['allowed_disks'] = $allowedDisks;
329+
330+
return $this;
331+
}
332+
323333
public function setThemeColor(string $hexColor): self
324334
{
325335
$this->config['theme']['primary_color'] = $hexColor;

src/Http/Controllers/Api/DownloadController.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ public function show(string $entityKey, ?string $instanceId = null)
1010
{
1111
$this->authorizationManager->check('view', $entityKey, $instanceId);
1212

13+
if (
14+
($allowedDisks = sharp()->config()->get('downloads.allowed_disks')) !== null // Legacy config
15+
&& $allowedDisks != '*'
16+
) {
17+
abort_if(! in_array(request()->get('disk'), $allowedDisks), 403);
18+
}
19+
1320
abort_if(
1421
! ($path = request()->get('path'))
1522
|| ! ($disk = request()->get('disk'))

tests/Http/Api/DownloadControllerTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,39 @@
7777
)
7878
->assertForbidden();
7979
});
80+
81+
it('allows to download a file of an allowed disk', function () {
82+
sharp()->config()->configureDownloads(['local']);
83+
84+
$file = UploadedFile::fake()->image('test.jpg', 600, 600);
85+
$file->storeAs('/files', 'test.jpg', ['disk' => 'local']);
86+
87+
$this
88+
->get(
89+
route('code16.sharp.download.show', [
90+
'entityKey' => 'person',
91+
'instanceId' => 1,
92+
'disk' => 'local',
93+
'path' => '/files/test.jpg',
94+
]),
95+
)
96+
->assertOk();
97+
});
98+
99+
it('does not allow to download a file of a not allowed disk', function () {
100+
sharp()->config()->configureDownloads(['s3']);
101+
102+
$file = UploadedFile::fake()->image('test.jpg', 600, 600);
103+
$file->storeAs('/files', 'test.jpg', ['disk' => 'local']);
104+
105+
$this
106+
->get(
107+
route('code16.sharp.download.show', [
108+
'entityKey' => 'person',
109+
'instanceId' => 1,
110+
'disk' => 'local',
111+
'path' => '/files/test.jpg',
112+
]),
113+
)
114+
->assertForbidden();
115+
});

0 commit comments

Comments
 (0)