From 26d83af298cdf577ed2e3599e75d1219e6029e06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20B=C3=B6hmer?= <mail@jan-boehmer.de>
Date: Tue, 14 May 2024 23:02:46 +0200
Subject: [PATCH] Use new settings systems for attachments settings

---
 .env                                          |  8 ---
 .gitignore                                    |  1 +
 config/parameters.yaml                        |  4 --
 config/services.yaml                          |  8 ---
 src/Controller/ToolsController.php            |  8 ++-
 src/Form/AttachmentFormType.php               | 11 ++--
 .../Attachments/AttachmentSubmitHandler.php   | 18 +++---
 src/Settings/AppSettings.php                  |  7 ++-
 .../{TestSettings.php => SystemSettings.php}  | 15 ++---
 .../SystemSettings/AttachmentsSettings.php    | 56 +++++++++++++++++++
 translations/messages.en.xlf                  | 36 ++++++++++++
 translations/validators.en.xlf                |  6 ++
 12 files changed, 129 insertions(+), 49 deletions(-)
 rename src/Settings/{TestSettings.php => SystemSettings.php} (79%)
 create mode 100644 src/Settings/SystemSettings/AttachmentsSettings.php

diff --git a/.env b/.env
index 8e48085e1..262043609 100644
--- a/.env
+++ b/.env
@@ -35,16 +35,8 @@ DEFAULT_TIMEZONE="Europe/Berlin"
 BASE_CURRENCY="EUR"
 # The name of this installation. This will be shown as title in the browser and in the header of the website
 INSTANCE_NAME="Part-DB"
-# Allow users to download attachments to the server by providing an URL
-# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
-ALLOW_ATTACHMENT_DOWNLOADS=0
-# Set this to 1, if the "download external files" checkbox should be checked by default for new attachments
-ATTACHMENT_DOWNLOAD_BY_DEFAULT=0
 # Use gravatars for user avatars, when user has no own avatar defined
 USE_GRAVATAR=0
-# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes)
-# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files
-MAX_ATTACHMENT_FILE_SIZE="100M"
 
 # The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
 # This must end with a slash!
diff --git a/.gitignore b/.gitignore
index b726f64c6..c8c59090d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 /.env.local
 /.env.local.php
 /.env.*.local
+/.env.local.bak
 /config/secrets/prod/prod.decrypt.private.php
 /public/bundles/
 /var/
diff --git a/config/parameters.yaml b/config/parameters.yaml
index 4caa5780c..205b31a86 100644
--- a/config/parameters.yaml
+++ b/config/parameters.yaml
@@ -34,11 +34,8 @@ parameters:
   ######################################################################################################################
   # Attachments and files
   ######################################################################################################################
-  partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%'    # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
-  partdb.attachments.download_by_default: '%env(bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT)%' # If this is set the 'download external files' checkbox is set by default for new attachments (only if allow_downloads is set to true)
   partdb.attachments.dir.media: 'public/media/'                                   # The folder where uploaded attachment files are saved (must be in public folder)
   partdb.attachments.dir.secure: 'uploads/'                                       # The folder where secured attachment files are saved (must not be in public/)
-  partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%'      # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes)
 
   ######################################################################################################################
   # Error pages
@@ -113,7 +110,6 @@ parameters:
   env(INSTANCE_NAME): 'Part-DB'
   env(BASE_CURRENCY): 'EUR'
   env(USE_GRAVATAR): '0'
-  env(MAX_ATTACHMENT_FILE_SIZE): '100M'
 
   env(REDIRECT_TO_HTTPS): 0
 
diff --git a/config/services.yaml b/config/services.yaml
index 292c532f2..7264783ed 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -89,17 +89,9 @@ services:
         tags:
             - { name: 'doctrine.event_subscriber' }
 
-    App\Form\AttachmentFormType:
-        arguments:
-            $allow_attachments_download: '%partdb.attachments.allow_downloads%'
-            $max_file_size: '%partdb.attachments.max_file_size%'
-            $download_by_default: '%partdb.attachments.download_by_default%'
-
     App\Services\Attachments\AttachmentSubmitHandler:
         arguments:
-            $allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
             $mimeTypes: '@mime_types'
-            $max_upload_size: '%partdb.attachments.max_file_size%'
 
     App\Services\LogSystem\EventCommentNeededHelper:
         arguments:
diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php
index 984e50eb2..040e418c3 100644
--- a/src/Controller/ToolsController.php
+++ b/src/Controller/ToolsController.php
@@ -28,6 +28,7 @@
 use App\Services\Misc\GitVersionInfo;
 use App\Services\Misc\DBInfoHelper;
 use App\Services\System\UpdateAvailableManager;
+use App\Settings\AppSettings;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Attribute\Route;
@@ -45,7 +46,8 @@ public function reelCalculator(): Response
 
     #[Route(path: '/server_infos', name: 'tools_server_infos')]
     public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper,
-        AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager): Response
+        AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager,
+        AppSettings $settings): Response
     {
         $this->denyAccessUnlessGranted('@system.server_infos');
 
@@ -66,10 +68,10 @@ public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHel
             'is_debug' => $this->getParameter('kernel.debug'),
             'email_sender' => $this->getParameter('partdb.mail.sender_email'),
             'email_sender_name' => $this->getParameter('partdb.mail.sender_name'),
-            'allow_attachments_downloads' => $this->getParameter('partdb.attachments.allow_downloads'),
+            'allow_attachments_downloads' => $settings->system->attachments->allowDownloads,
             'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'),
             'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'),
-            'configured_max_file_size' => $this->getParameter('partdb.attachments.max_file_size'),
+            'configured_max_file_size' => $settings->system->attachments->maxFileSize,
             'effective_max_file_size' => $attachmentSubmitHandler->getMaximumAllowedUploadSize(),
             'saml_enabled' => $this->getParameter('partdb.saml.enabled'),
 
diff --git a/src/Form/AttachmentFormType.php b/src/Form/AttachmentFormType.php
index 109c66025..b9963bed4 100644
--- a/src/Form/AttachmentFormType.php
+++ b/src/Form/AttachmentFormType.php
@@ -22,6 +22,7 @@
 
 namespace App\Form;
 
+use App\Settings\SystemSettings\AttachmentsSettings;
 use Symfony\Bundle\SecurityBundle\Security;
 use App\Entity\Attachments\Attachment;
 use App\Entity\Attachments\AttachmentType;
@@ -54,9 +55,7 @@ public function __construct(
         protected Security $security,
         protected AttachmentSubmitHandler $submitHandler,
         protected TranslatorInterface $translator,
-        protected bool $allow_attachments_download,
-        protected bool $download_by_default,
-        protected string $max_file_size
+        protected AttachmentsSettings $settings,
     ) {
     }
 
@@ -108,7 +107,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
             'required' => false,
             'label' => 'attachment.edit.download_url',
             'mapped' => false,
-            'disabled' => !$this->allow_attachments_download,
+            'disabled' => !$this->settings->allowDownloads,
         ]);
 
         $builder->add('file', FileType::class, [
@@ -177,7 +176,7 @@ static function (FormEvent $event): void {
 
         //If the attachment should be downloaded by default (and is download allowed at all), register a listener,
         // which sets the downloadURL checkbox to true for new attachments
-        if ($this->download_by_default && $this->allow_attachments_download) {
+        if ($this->settings->downloadByDefault && $this->settings->allowDownloads) {
             $builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event): void {
                 $form = $event->getForm();
                 $attachment = $form->getData();
@@ -204,7 +203,7 @@ public function configureOptions(OptionsResolver $resolver): void
     {
         $resolver->setDefaults([
             'data_class' => Attachment::class,
-            'max_file_size' => $this->max_file_size,
+            'max_file_size' => $this->settings->maxFileSize,
             'allow_builtins' => true,
         ]);
     }
diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php
index d5fe9370a..2cd9a153e 100644
--- a/src/Services/Attachments/AttachmentSubmitHandler.php
+++ b/src/Services/Attachments/AttachmentSubmitHandler.php
@@ -40,6 +40,7 @@
 use App\Entity\Attachments\SupplierAttachment;
 use App\Entity\Attachments\UserAttachment;
 use App\Exceptions\AttachmentDownloadException;
+use App\Settings\SystemSettings\AttachmentsSettings;
 use Hshn\Base64EncodedFile\HttpFoundation\File\Base64EncodedFile;
 use Hshn\Base64EncodedFile\HttpFoundation\File\UploadedBase64EncodedFile;
 use const DIRECTORY_SEPARATOR;
@@ -64,12 +65,13 @@ class AttachmentSubmitHandler
         'asp', 'cgi', 'py', 'pl', 'exe', 'aspx', 'js', 'mjs', 'jsp', 'css', 'jar', 'html', 'htm', 'shtm', 'shtml', 'htaccess',
         'htpasswd', ''];
 
-    public function __construct(protected AttachmentPathResolver $pathResolver, protected bool $allow_attachments_downloads,
-        protected HttpClientInterface $httpClient, protected MimeTypesInterface $mimeTypes,
-        protected FileTypeFilterTools $filterTools, /**
-         * @var string The user configured maximum upload size. This is a string like "10M" or "1G" and will be converted to
-         */
-        protected string $max_upload_size)
+    public function __construct(
+        protected AttachmentPathResolver $pathResolver,
+        protected HttpClientInterface $httpClient,
+        protected MimeTypesInterface $mimeTypes,
+        protected FileTypeFilterTools $filterTools,
+        protected AttachmentsSettings $settings,
+    )
     {
         //The mapping used to determine which folder will be used for an attachment type
         $this->folder_mapping = [
@@ -334,7 +336,7 @@ protected function moveFile(Attachment $attachment, bool $secure_location): Atta
     protected function downloadURL(Attachment $attachment, bool $secureAttachment): Attachment
     {
         //Check if we are allowed to download files
-        if (!$this->allow_attachments_downloads) {
+        if (!$this->settings->allowDownloads) {
             throw new RuntimeException('Download of attachments is not allowed!');
         }
 
@@ -472,7 +474,7 @@ public function getMaximumAllowedUploadSize(): int
         $this->max_upload_size_bytes = min(
             $this->parseFileSizeString(ini_get('post_max_size')),
             $this->parseFileSizeString(ini_get('upload_max_filesize')),
-            $this->parseFileSizeString($this->max_upload_size),
+            $this->parseFileSizeString($this->settings->maxFileSize)
         );
 
         return $this->max_upload_size_bytes;
diff --git a/src/Settings/AppSettings.php b/src/Settings/AppSettings.php
index fd0a8c6d1..6bd198940 100644
--- a/src/Settings/AppSettings.php
+++ b/src/Settings/AppSettings.php
@@ -24,6 +24,7 @@
 namespace App\Settings;
 
 use App\Settings\InfoProviderSystem\InfoProviderSettings;
+use App\Settings\SystemSettings\AttachmentsSettings;
 use Jbtronics\SettingsBundle\Settings\EmbeddedSettings;
 use Jbtronics\SettingsBundle\Settings\Settings;
 use Jbtronics\SettingsBundle\Settings\SettingsTrait;
@@ -33,9 +34,11 @@ class AppSettings
 {
     use SettingsTrait;
 
+
     #[EmbeddedSettings()]
-    public ?InfoProviderSettings $infoProviders = null;
+    public ?SystemSettings $system;
+
 
     #[EmbeddedSettings()]
-    public ?TestSettings $test = null;
+    public ?InfoProviderSettings $infoProviders = null;
 }
\ No newline at end of file
diff --git a/src/Settings/TestSettings.php b/src/Settings/SystemSettings.php
similarity index 79%
rename from src/Settings/TestSettings.php
rename to src/Settings/SystemSettings.php
index 420205465..b8aafd57f 100644
--- a/src/Settings/TestSettings.php
+++ b/src/Settings/SystemSettings.php
@@ -23,18 +23,13 @@
 
 namespace App\Settings;
 
+use App\Settings\SystemSettings\AttachmentsSettings;
+use Jbtronics\SettingsBundle\Settings\EmbeddedSettings;
 use Jbtronics\SettingsBundle\Settings\Settings;
-use Jbtronics\SettingsBundle\Settings\SettingsParameter;
 
 #[Settings]
-class TestSettings
+class SystemSettings
 {
-    #[SettingsParameter()]
-    public bool $bool = false;
-
-    #[SettingsParameter()]
-    public int $int = 0;
-
-    #[SettingsParameter()]
-    public float $float = 0.0;
+    #[EmbeddedSettings()]
+    public ?AttachmentsSettings $attachments = null;
 }
\ No newline at end of file
diff --git a/src/Settings/SystemSettings/AttachmentsSettings.php b/src/Settings/SystemSettings/AttachmentsSettings.php
new file mode 100644
index 000000000..e7c49fd77
--- /dev/null
+++ b/src/Settings/SystemSettings/AttachmentsSettings.php
@@ -0,0 +1,56 @@
+<?php
+/*
+ * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
+ *
+ *  Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU Affero General Public License as published
+ *  by the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Affero General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Affero General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+
+namespace App\Settings\SystemSettings;
+
+use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
+use Jbtronics\SettingsBundle\Settings\Settings;
+use Jbtronics\SettingsBundle\Settings\SettingsParameter;
+use Symfony\Component\Translation\TranslatableMessage as TM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[Settings(label: new TM("settings.system.attachments"))]
+class AttachmentsSettings
+{
+    #[SettingsParameter(
+        label: new TM("settings.system.attachments.maxFileSize"),
+        description: new TM("settings.system.attachments.maxFileSize.help"),
+        envVar: "MAX_ATTACHMENT_FILE_SIZE", envVarMode: EnvVarMode::OVERWRITE
+    )]
+    #[Assert\Regex("/^([1-9][0-9]*)([KMG])?$/", message: "validator.fileSize.invalidFormat")]
+    public string $maxFileSize = '100M';
+
+    #[SettingsParameter(
+        label: new TM("settings.system.attachments.allowDownloads"),
+        description: new TM("settings.system.attachments.allowDownloads.help"),
+        formOptions: ['help_html' => true],
+        envVar: "bool:ALLOW_ATTACHMENT_DOWNLOADS", envVarMode: EnvVarMode::OVERWRITE
+    )]
+    public bool $allowDownloads = false;
+
+    #[SettingsParameter(
+        label: new TM("settings.system.attachments.downloadByDefault"),
+        envVar: "bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT", envVarMode: EnvVarMode::OVERWRITE
+    )]
+    public bool $downloadByDefault = false;
+}
\ No newline at end of file
diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf
index 636b5b90f..a01658851 100644
--- a/translations/messages.en.xlf
+++ b/translations/messages.en.xlf
@@ -12377,5 +12377,41 @@ Please note, that you can not impersonate a disabled user. If you try you will g
         <target>Currency</target>
       </segment>
     </unit>
+    <unit id="g0AgaAf" name="settings.system.attachments">
+      <segment>
+        <source>settings.system.attachments</source>
+        <target><![CDATA[Attachments & Files]]></target>
+      </segment>
+    </unit>
+    <unit id="1wsDvqz" name="settings.system.attachments.maxFileSize">
+      <segment>
+        <source>settings.system.attachments.maxFileSize</source>
+        <target>Maximum file size</target>
+      </segment>
+    </unit>
+    <unit id="Gn0P1Bv" name="settings.system.attachments.maxFileSize.help">
+      <segment>
+        <source>settings.system.attachments.maxFileSize.help</source>
+        <target>The maximum size of files that can be uploaded. Please note that this is also limited by PHP configuration.</target>
+      </segment>
+    </unit>
+    <unit id="DyIfVYH" name="settings.system.attachments.allowDownloads">
+      <segment>
+        <source>settings.system.attachments.allowDownloads</source>
+        <target>Allow downloading of external files</target>
+      </segment>
+    </unit>
+    <unit id="jD.pgZL" name="settings.system.attachments.allowDownloads.help">
+      <segment>
+        <source>settings.system.attachments.allowDownloads.help</source>
+        <target><![CDATA[With this option users can download external files into Part-DB by providing an URL. <b>Attention: This can be a security issue, as it might allow users to access intranet ressources via Part-DB!</b>]]></target>
+      </segment>
+    </unit>
+    <unit id="HRxjf.u" name="settings.system.attachments.downloadByDefault">
+      <segment>
+        <source>settings.system.attachments.downloadByDefault</source>
+        <target>Download new attachment URLs by default</target>
+      </segment>
+    </unit>
   </file>
 </xliff>
diff --git a/translations/validators.en.xlf b/translations/validators.en.xlf
index cec89ea6d..5c4371293 100644
--- a/translations/validators.en.xlf
+++ b/translations/validators.en.xlf
@@ -347,5 +347,11 @@
         <target>Due to technical limitations, it is not possible to select dates after the 2038-01-19 on 32-bit systems!</target>
       </segment>
     </unit>
+    <unit id="89nojXY" name="validator.fileSize.invalidFormat">
+      <segment>
+        <source>validator.fileSize.invalidFormat</source>
+        <target>Invalid file size format. Use an integer number plus K, M, G as suffix for Kilo, Mega or Gigabytes.</target>
+      </segment>
+    </unit>
   </file>
 </xliff>