diff --git a/docs/configuration.md b/docs/configuration.md index b4c3d747..709c39b3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -86,9 +86,6 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept * `ATTACHMENT_DOWNLOAD_BY_DEFAULT`: When this is set to 1, the "download external file" checkbox is checked by default when adding a new attachment. Otherwise, it is unchecked by default. Use this if you wanna download all attachments locally by default. Attachment download is only possible, when `ALLOW_ATTACHMENT_DOWNLOADS` is set to 1. -* `ATTACHMENT_SHOW_HTML_FILES`: When enabled, user uploaded HTML attachments can be viewed directly in the browser. - Many potential malicious functions are restricted, still this is a potential security risk and should only be enabled, - if you trust the users who can upload files. When set to 0, HTML files are rendered as plain text. * `USE_GRAVATAR`: Set to `1` to use [gravatar.com](https://gravatar.com/) images for user avatars (as long as they have not set their own picture). The users browsers have to download the pictures from a third-party (gravatar) server, so this might be a privacy risk. diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php index 01aeab11..81369e12 100644 --- a/src/Controller/AttachmentFileController.php +++ b/src/Controller/AttachmentFileController.php @@ -30,7 +30,6 @@ use App\Form\Filters\AttachmentFilterType; use App\Services\Attachments\AttachmentManager; use App\Services\Trees\NodesListBuilder; use App\Settings\BehaviorSettings\TableSettings; -use App\Settings\SystemSettings\AttachmentsSettings; use Omines\DataTablesBundle\DataTableFactory; use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -42,93 +41,11 @@ use Symfony\Component\Routing\Attribute\Route; class AttachmentFileController extends AbstractController { - - public function __construct(private readonly AttachmentManager $helper) - { - - } - - #[Route(path: '/attachment/{id}/sandbox', name: 'attachment_html_sandbox')] - public function htmlSandbox(Attachment $attachment, AttachmentsSettings $attachmentsSettings): Response - { - //Check if the sandbox is enabled in the settings, as it can be a security risk if used without proper precautions, so it should be opt-in - if (!$attachmentsSettings->showHTMLAttachments) { - throw $this->createAccessDeniedException('The HTML sandbox for attachments is disabled in the settings, as it can be a security risk if used without proper precautions. Please enable it in the settings if you want to use it.'); - } - - $this->checkPermissions($attachment); - - $file_path = $this->helper->toAbsoluteInternalFilePath($attachment); - - $attachmentContent = file_get_contents($file_path); - - $response = $this->render('attachments/html_sandbox.html.twig', [ - 'attachment' => $attachment, - 'content' => $attachmentContent, - ]); - - //Set an CSP that allows to run inline scripts, styles and images from external ressources, but does not allow any connections or others. - //Also set the sandbox CSP directive with only "allow-script" to run basic scripts - $response->headers->set('Content-Security-Policy', "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline' *; img-src data: *; sandbox allow-scripts allow-downloads allow-modals;"); - - //Forbid to embed the attachment render page in an iframe to prevent clickjacking, as it is not used anywhere else for now - $response->headers->set('X-Frame-Options', 'DENY'); - - return $response; - } - /** * Download the selected attachment. */ #[Route(path: '/attachment/{id}/download', name: 'attachment_download')] - public function download(Attachment $attachment): BinaryFileResponse - { - $this->checkPermissions($attachment); - - $file_path = $this->helper->toAbsoluteInternalFilePath($attachment); - $response = new BinaryFileResponse($file_path); - - $response = $this->forbidHTMLContentType($response); - - //Set header content disposition, so that the file will be downloaded - $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $attachment->getFilename()); - - return $response; - } - - /** - * View the attachment. - */ - #[Route(path: '/attachment/{id}/view', name: 'attachment_view')] - public function view(Attachment $attachment): BinaryFileResponse - { - $this->checkPermissions($attachment); - - $file_path = $this->helper->toAbsoluteInternalFilePath($attachment); - $response = new BinaryFileResponse($file_path); - - $response = $this->forbidHTMLContentType($response); - - //Set header content disposition, so that the file will be downloaded - $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $attachment->getFilename()); - - return $response; - } - - private function forbidHTMLContentType(BinaryFileResponse $response): BinaryFileResponse - { - $mimeType = $response->getFile()->getMimeType(); - - if ($mimeType === 'text/html') { - $mimeType = 'text/plain'; - } - - $response->headers->set('Content-Type', $mimeType); - - return $response; - } - - private function checkPermissions(Attachment $attachment): void + public function download(Attachment $attachment, AttachmentManager $helper): BinaryFileResponse { $this->denyAccessUnlessGranted('read', $attachment); @@ -140,9 +57,46 @@ class AttachmentFileController extends AbstractController throw $this->createNotFoundException('The file for this attachment is external and not stored locally!'); } - if (!$this->helper->isInternalFileExisting($attachment)) { + if (!$helper->isInternalFileExisting($attachment)) { throw $this->createNotFoundException('The file associated with the attachment is not existing!'); } + + $file_path = $helper->toAbsoluteInternalFilePath($attachment); + $response = new BinaryFileResponse($file_path); + + //Set header content disposition, so that the file will be downloaded + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT); + + return $response; + } + + /** + * View the attachment. + */ + #[Route(path: '/attachment/{id}/view', name: 'attachment_view')] + public function view(Attachment $attachment, AttachmentManager $helper): BinaryFileResponse + { + $this->denyAccessUnlessGranted('read', $attachment); + + if ($attachment->isSecure()) { + $this->denyAccessUnlessGranted('show_private', $attachment); + } + + if (!$attachment->hasInternal()) { + throw $this->createNotFoundException('The file for this attachment is external and not stored locally!'); + } + + if (!$helper->isInternalFileExisting($attachment)) { + throw $this->createNotFoundException('The file associated with the attachment is not existing!'); + } + + $file_path = $helper->toAbsoluteInternalFilePath($attachment); + $response = new BinaryFileResponse($file_path); + + //Set header content disposition, so that the file will be downloaded + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE); + + return $response; } #[Route(path: '/attachment/list', name: 'attachment_list')] diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index e0d1bd9d..d4b15ac7 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -296,22 +296,6 @@ abstract class Attachment extends AbstractNamedDBElement return in_array(strtolower($extension), static::MODEL_EXTS, true); } - /** - * Returns true if this is a locally stored HTML file, which can be shown by the sandbox viewer. - * This is the case if we have an internal path with a html extension. - * @return bool - */ - public function isLocalHTMLFile(): bool - { - if($this->hasInternal()){ - - $extension = pathinfo($this->getFilename(), PATHINFO_EXTENSION); - - return in_array(strtolower($extension), ['html', 'htm'], true); - } - return false; - } - /** * Checks if this attachment has a path to an external file * diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php index 81a83f0c..c7e69257 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -137,10 +137,7 @@ class AttachmentSubmitHandler $attachment->getName() ); - // Generate a 12-character URL-safe random string, which should avoid collisions and prevent from guessing file paths. - $random = str_replace(['+', '/', '='], ['0', '1', '2'], base64_encode(random_bytes(9))); - - return $safeName.'-'.$random.'.'.$extension; + return $safeName.'-'.uniqid('', false).'.'.$extension; } /** diff --git a/src/Services/Attachments/AttachmentURLGenerator.php b/src/Services/Attachments/AttachmentURLGenerator.php index 89856650..e505408f 100644 --- a/src/Services/Attachments/AttachmentURLGenerator.php +++ b/src/Services/Attachments/AttachmentURLGenerator.php @@ -22,7 +22,6 @@ declare(strict_types=1); namespace App\Services\Attachments; -use App\Settings\SystemSettings\AttachmentsSettings; use Imagine\Exception\RuntimeException; use App\Entity\Attachments\Attachment; use InvalidArgumentException; @@ -41,7 +40,7 @@ class AttachmentURLGenerator public function __construct(protected Packages $assets, protected AttachmentPathResolver $pathResolver, protected UrlGeneratorInterface $urlGenerator, protected AttachmentManager $attachmentHelper, - protected CacheManager $thumbnailManager, protected LoggerInterface $logger, private readonly AttachmentsSettings $attachmentsSettings) + protected CacheManager $thumbnailManager, protected LoggerInterface $logger) { //Determine a normalized path to the public folder (assets are relative to this folder) $this->public_path = $this->pathResolver->parameterToAbsolutePath('public'); @@ -100,10 +99,6 @@ class AttachmentURLGenerator return null; } - if ($this->attachmentsSettings->showHTMLAttachments && $attachment->isLocalHTMLFile()) { - return $this->urlGenerator->generate('attachment_html_sandbox', ['id' => $attachment->getID()]); - } - $asset_path = $this->absolutePathToAssetPath($absolute_path); //If path is not relative to public path or marked as secure, serve it via controller if (null === $asset_path || $attachment->isSecure()) { diff --git a/src/Services/InfoProviderSystem/Providers/CanopyProvider.php b/src/Services/InfoProviderSystem/Providers/CanopyProvider.php index 18864a49..131db15f 100644 --- a/src/Services/InfoProviderSystem/Providers/CanopyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/CanopyProvider.php @@ -202,10 +202,6 @@ class CanopyProvider implements InfoProviderInterface $product = $response->toArray()['data']['amazonProduct']; - if ($product === null) { - throw new \RuntimeException("Product with ASIN $id not found"); - } - return new PartDetailDTO( provider_key: $this->getProviderKey(), provider_id: $product['asin'], diff --git a/src/Settings/SystemSettings/AttachmentsSettings.php b/src/Settings/SystemSettings/AttachmentsSettings.php index 5aa3f91d..6d15c639 100644 --- a/src/Settings/SystemSettings/AttachmentsSettings.php +++ b/src/Settings/SystemSettings/AttachmentsSettings.php @@ -58,11 +58,4 @@ class AttachmentsSettings envVar: "bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT", envVarMode: EnvVarMode::OVERWRITE )] public bool $downloadByDefault = false; - - #[SettingsParameter( - label: new TM("settings.system.attachments.showHTMLAttachments"), - description: new TM("settings.system.attachments.showHTMLAttachments.help"), - envVar: "bool:ATTACHMENT_SHOW_HTML_FILES", envVarMode: EnvVarMode::OVERWRITE - )] - public bool $showHTMLAttachments = false; -} +} \ No newline at end of file diff --git a/templates/attachments/html_sandbox.html.twig b/templates/attachments/html_sandbox.html.twig deleted file mode 100644 index 389ebc2e..00000000 --- a/templates/attachments/html_sandbox.html.twig +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - {# The content block is already escaped. so we must not escape it again. #} -