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
22 changes: 6 additions & 16 deletions resources/js/form/components/fields/upload/Upload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -477,20 +477,15 @@
</template>
<div class="flex-1 min-w-0">
<div class="text-sm truncate">
<template v-if="value?.path">
<template v-if="value?.download_url">
<TooltipProvider>
<Tooltip :delay-duration="0" disable-hoverable-content>
<TooltipTrigger as-child>
<a class="text-foreground underline underline-offset-4 decoration-foreground/20 hover:decoration-foreground"
:href="route('code16.sharp.download.show', {
entityKey: form.entityKey,
instanceId: form.instanceId,
disk: value.disk,
path: value.path,
})"
:download="value?.name"
:href="value.download_url"
:download="value.name"
>
{{ value?.name }}
{{ value.name }}
</a>
</TooltipTrigger>

Expand Down Expand Up @@ -527,16 +522,11 @@
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<template v-if="value?.path">
<template v-if="value?.download_url">
<DropdownMenuItem
as="a"
:download="value.name ?? ''"
:href="route('code16.sharp.download.show', {
entityKey: form.entityKey,
instanceId: form.instanceId,
disk: value.disk,
path: value.path,
})"
:href="value.download_url"
>
{{ __('sharp::form.upload.download_link') }}
</DropdownMenuItem>
Expand Down
79 changes: 38 additions & 41 deletions resources/js/show/components/fields/File.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,27 @@
</template>
<div class="flex flex-col flex-1 min-w-0">
<div class="truncate text-sm font-medium">
<TooltipProvider>
<Tooltip :delay-duration="0" disable-hoverable-content>
<TooltipTrigger as-child>
<a class="text-foreground underline underline-offset-4 decoration-foreground/20 hover:underline hover:decoration-foreground"
:href="route('code16.sharp.download.show', {
entityKey: show.entityKey,
instanceId: show.instanceId,
disk: value.disk,
path: value.path,
})"
:download="value.name ?? ''"
>
{{ value.name }}
</a>
</TooltipTrigger>
<template v-if="value.download_url">
<TooltipProvider>
<Tooltip :delay-duration="0" disable-hoverable-content>
<TooltipTrigger as-child>
<a class="text-foreground underline underline-offset-4 decoration-foreground/20 hover:underline hover:decoration-foreground"
:href="value.download_url"
:download="value.name ?? ''"
>
{{ value.name }}
</a>
</TooltipTrigger>

<TooltipContent class="pointer-events-none" :side-offset="10">
{{ __('sharp::form.upload.download_tooltip') }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipContent class="pointer-events-none" :side-offset="10">
{{ __('sharp::form.upload.download_tooltip') }}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</template>
<template v-else>
{{ value.name }}
</template>
</div>
<template v-if="legend">
<div class="mt-2 text-muted-foreground text-sm">
Expand All @@ -87,27 +87,24 @@
</div>
</div>
</div>
<DropdownMenu :modal="false">
<DropdownMenuTrigger as-child>
<Button class="self-center" variant="ghost" size="icon">
<MoreHorizontal class="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
as="a"
:download="value.name ?? ''"
:href="route('code16.sharp.download.show', {
entityKey: show.entityKey,
instanceId: show.instanceId,
disk: value.disk,
path: value.path,
})"
>
{{ __('sharp::show.file.download') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<template v-if="value.download_url">
<DropdownMenu :modal="false">
<DropdownMenuTrigger as-child>
<Button class="self-center" variant="ghost" size="icon">
<MoreHorizontal class="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
as="a"
:download="value.name ?? ''"
:href="value.download_url"
>
{{ __('sharp::show.file.download') }}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>
</template>
</div>
</ShowFieldLayout>
Expand Down
2 changes: 2 additions & 0 deletions resources/js/types/generated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ export type FormUploadFieldValueData = {
thumbnail: string | null;
editable_thumbnail: string | null;
playable_preview_url: string | null;
download_url: string | null;
uploaded: boolean | null;
transformed: boolean | null;
not_found: boolean | null;
Expand Down Expand Up @@ -929,6 +930,7 @@ export type ShowFileFieldData = {
path: string;
thumbnail: string;
playable_preview_url: string;
download_url: string;
size: number;
mime_type: string;
};
Expand Down
1 change: 1 addition & 0 deletions src/Data/Form/Fields/FormUploadFieldValueData.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public function __construct(
public ?string $thumbnail,
public ?string $editable_thumbnail,
public ?string $playable_preview_url,
public ?string $download_url,
public ?bool $uploaded,
public ?bool $transformed,
public ?bool $not_found,
Expand Down
1 change: 1 addition & 0 deletions src/Data/Show/Fields/ShowFileFieldData.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class ShowFileFieldData extends Data
'path' => 'string',
'thumbnail' => 'string',
'playable_preview_url' => 'string',
'download_url' => 'string',
'size' => 'int',
'mime_type' => 'string',
])]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
use Intervention\Image\Exceptions\DecoderException;

Expand Down Expand Up @@ -79,6 +80,7 @@ public function apply($value, $instance = null, $attribute = null)
'disk',
'thumbnail',
'playable_preview_url',
'download_url',
'size',
'mime_type',
'filters',
Expand Down Expand Up @@ -108,6 +110,15 @@ protected function transformUpload(SharpUploadModel $upload): array
'mime_type' => $upload->mime_type,
'thumbnail' => $this->getThumbnailUrl($upload),
'playable_preview_url' => $this->getPlayableMediaUrl($upload),
'download_url' => URL::temporarySignedRoute(
'code16.sharp.download.show',
now()->plus(minutes: config('session.lifetime')),
[
'entityKey' => sharp()->context()->entityKey(),
'instanceId' => sharp()->context()->instanceId(),
'disk' => $upload->disk,
'path' => $upload->file_name,
]),
'size' => $upload->size,
]
: [],
Expand Down
4 changes: 4 additions & 0 deletions src/Http/Controllers/Api/DownloadController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class DownloadController extends ApiController
{
public function show(string $globalFilter, string $entityKey, ?string $instanceId = null)
{
if (! request()->hasValidSignature()) {
abort(401);
}

$this->authorizationManager->check('view', $entityKey, $instanceId);

if (
Expand Down
26 changes: 20 additions & 6 deletions tests/Http/Api/DownloadControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Code16\Sharp\Utils\Entities\SharpEntityManager;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;

beforeEach(function () {
Storage::fake('local');
Expand All @@ -17,7 +18,7 @@

$this
->get(
route('code16.sharp.download.show', [
URL::temporarySignedRoute('code16.sharp.download.show', now()->addMinutes(120), [
'entityKey' => 'person',
'instanceId' => 1,
'disk' => 'local',
Expand All @@ -35,7 +36,7 @@

$this
->get(
route('code16.sharp.download.show', [
URL::temporarySignedRoute('code16.sharp.download.show', now()->addMinutes(120), [
'entityKey' => 'person',
'instanceId' => 1,
'disk' => 'local',
Expand All @@ -50,7 +51,7 @@
it('returns a 404 for a missing file', function () {
$this
->get(
route('code16.sharp.download.show', [
URL::temporarySignedRoute('code16.sharp.download.show', now()->addMinutes(120), [
'entityKey' => 'person',
'instanceId' => 1,
'disk' => 'local',
Expand All @@ -60,14 +61,27 @@
->assertNotFound();
});

it('returns a 401 for an invalid signature', function () {
$this
->get(
route('code16.sharp.download.show', [
'entityKey' => 'person',
'instanceId' => 1,
'disk' => 'local',
'path' => '/files/test.jpg',
]),
)
->assertStatus(401);
});

it('does not allow to download a file without authorization', function () {
app(SharpEntityManager::class)
->entityFor('person')
->setProhibitedActions(['view']);

$this
->get(
route('code16.sharp.download.show', [
URL::temporarySignedRoute('code16.sharp.download.show', now()->addMinutes(120), [
'entityKey' => 'person',
'instanceId' => 1,
'disk' => 'local',
Expand All @@ -85,7 +99,7 @@

$this
->get(
route('code16.sharp.download.show', [
URL::temporarySignedRoute('code16.sharp.download.show', now()->addMinutes(120), [
'entityKey' => 'person',
'instanceId' => 1,
'disk' => 'local',
Expand All @@ -103,7 +117,7 @@

$this
->get(
route('code16.sharp.download.show', [
URL::temporarySignedRoute('code16.sharp.download.show', now()->addMinutes(120), [
'entityKey' => 'person',
'instanceId' => 1,
'disk' => 'local',
Expand Down
Loading
Loading