From fdfb099cb5c112d9c4451a904c91f5536777cf59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Thu, 31 Oct 2019 22:37:54 +0100 Subject: [PATCH] Allow to restrict the file extensions for a attachment type. --- .gitignore | 5 + config/services.yaml | 4 + .../AdminPages/AttachmentTypeController.php | 3 +- src/Entity/Attachments/AttachmentType.php | 6 +- .../AdminPages/AttachmentTypeAdminForm.php | 77 ++++++++ src/Form/AttachmentFormType.php | 4 +- .../Attachments/FileTypeFilterTools.php | 184 ++++++++++++++++++ .../Constraints/AllowedFileExtension.php | 44 +++++ .../AllowedFileExtensionValidator.php | 91 +++++++++ src/Validator/Constraints/NoLockout.php | 1 + src/Validator/Constraints/UrlOrBuiltin.php | 1 + src/Validator/Constraints/ValidFileFilter.php | 44 +++++ .../Constraints/ValidFileFilterValidator.php | 77 ++++++++ .../AdminPages/AttachmentTypeAdmin.html.twig | 4 + .../AbstractAdminControllerTest.php | 7 + .../AttachmentTypeControllerTest.php | 4 + .../AdminPages/CategoryControllerTest.php | 4 + .../AdminPages/DeviceControllerTest.php | 4 + .../AdminPages/FootprintControllerTest.php | 4 + .../AdminPages/ManufacturerControllerTest.php | 4 + .../MeasurementUnitControllerTest.php | 4 + .../StorelocationControllerTest.php | 4 + .../AdminPages/SupplierControllerTest.php | 4 + tests/Controller/RedirectControllerTest.php | 7 + .../Attachments/FileTypeFilterToolsTest.php | 127 ++++++++++++ 25 files changed, 714 insertions(+), 4 deletions(-) create mode 100644 src/Form/AdminPages/AttachmentTypeAdminForm.php create mode 100644 src/Services/Attachments/FileTypeFilterTools.php create mode 100644 src/Validator/Constraints/AllowedFileExtension.php create mode 100644 src/Validator/Constraints/AllowedFileExtensionValidator.php create mode 100644 src/Validator/Constraints/ValidFileFilter.php create mode 100644 src/Validator/Constraints/ValidFileFilterValidator.php create mode 100644 tests/Services/Attachments/FileTypeFilterToolsTest.php diff --git a/.gitignore b/.gitignore index 098c0d4f..df30280e 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,8 @@ yarn-error.log ###> liip/imagine-bundle ### /public/media/cache/ ###< liip/imagine-bundle ### + +###> phpunit/phpunit ### +/phpunit.xml +.phpunit.result.cache +###< phpunit/phpunit ### diff --git a/config/services.yaml b/config/services.yaml index fd5e03b2..ec8db314 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -104,6 +104,10 @@ services: $footprints_path: 'public/img/footprints' $models_path: null + App\Services\Attachments\FileTypeFilterTools: + arguments: + $mimeTypes: '@mime_types' + App\Services\TranslationExtractor\PermissionExtractor: tags: - { name: 'translation.extractor', alias: 'permissionExtractor'} \ No newline at end of file diff --git a/src/Controller/AdminPages/AttachmentTypeController.php b/src/Controller/AdminPages/AttachmentTypeController.php index 1d439a86..c2f1909a 100644 --- a/src/Controller/AdminPages/AttachmentTypeController.php +++ b/src/Controller/AdminPages/AttachmentTypeController.php @@ -34,6 +34,7 @@ namespace App\Controller\AdminPages; use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Form\AdminPages\AttachmentTypeAdminForm; use App\Form\AdminPages\BaseEntityAdminForm; use App\Services\EntityExporter; use App\Services\EntityImporter; @@ -52,7 +53,7 @@ class AttachmentTypeController extends BaseAdminController protected $entity_class = AttachmentType::class; protected $twig_template = 'AdminPages/AttachmentTypeAdmin.html.twig'; - protected $form_class = BaseEntityAdminForm::class; + protected $form_class = AttachmentTypeAdminForm::class; protected $route_base = 'attachment_type'; protected $attachment_class = AttachmentTypeAttachment::class; diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index 4b77eae3..bfa0b6b6 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -56,6 +56,7 @@ use App\Entity\Base\StructuralDBElement; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use App\Validator\Constraints\ValidFileFilter; /** * Class AttachmentType. @@ -91,6 +92,7 @@ class AttachmentType extends StructuralDBElement /** * @var string * @ORM\Column(type="text") + * @ValidFileFilter */ protected $filetype_filter = ""; @@ -112,6 +114,8 @@ class AttachmentType extends StructuralDBElement /** * Gets an filter, which file types are allowed for attachment files. + * Must be in the format of accept attribute + * (See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers). * @return string */ public function getFiletypeFilter(): string @@ -129,8 +133,6 @@ class AttachmentType extends StructuralDBElement return $this; } - - /** * Returns the ID as an string, defined by the element class. * This should have a form like P000014, for a part with ID 14. diff --git a/src/Form/AdminPages/AttachmentTypeAdminForm.php b/src/Form/AdminPages/AttachmentTypeAdminForm.php new file mode 100644 index 00000000..6faaa9c4 --- /dev/null +++ b/src/Form/AdminPages/AttachmentTypeAdminForm.php @@ -0,0 +1,77 @@ +filterTools = $filterTools; + parent::__construct($security, $params, $trans); + } + + protected function additionalFormElements(FormBuilderInterface $builder, array $options, NamedDBElement $entity) + { + $is_new = $entity->getID() === null; + + $builder->add('filetype_filter', TextType::class, ['required' => false, + 'label' => $this->trans->trans('attachment_type.edit.filetype_filter'), + 'help' => $this->trans->trans('attachment_type.edit.filetype_filter.help'), + 'attr' => ['placeholder' => $this->trans->trans('attachment_type.edit.filetype_filter.placeholder')], + 'empty_data' => '', + 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity)]); + + //Normalize data before writing it to database + $builder->get('filetype_filter')->addViewTransformer(new CallbackTransformer( + function ($value) { + return $value; + }, + function ($value) { + return $this->filterTools->normalizeFilterString($value); + } + )); + } +} \ No newline at end of file diff --git a/src/Form/AttachmentFormType.php b/src/Form/AttachmentFormType.php index 355c47f5..ab022878 100644 --- a/src/Form/AttachmentFormType.php +++ b/src/Form/AttachmentFormType.php @@ -37,6 +37,7 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Base\StructuralDBElement; use App\Form\Type\StructuralEntityType; use App\Services\Attachments\AttachmentManager; +use App\Validator\Constraints\AllowedFileExtension; use App\Validator\Constraints\UrlOrBuiltin; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -120,9 +121,10 @@ class AttachmentFormType extends AbstractType 'required' => false, 'attr' => ['class' => 'file', 'data-show-preview' => 'false', 'data-show-upload' => 'false'], 'constraints' => [ + new AllowedFileExtension(), new File([ 'maxSize' => $options['max_file_size'] - ]) + ]), ] ]); diff --git a/src/Services/Attachments/FileTypeFilterTools.php b/src/Services/Attachments/FileTypeFilterTools.php new file mode 100644 index 00000000..ff3560e1 --- /dev/null +++ b/src/Services/Attachments/FileTypeFilterTools.php @@ -0,0 +1,184 @@ + accept uses. + * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers for + * more details. + * @package App\Services\Attachments + */ +class FileTypeFilterTools +{ + + //The file extensions that will be used for the 'video/*', 'image/*', 'audio/*' placeholders + //These file formats can be directly played in common browesers + //Source: https://www.chromium.org/audio-video + protected const IMAGE_EXTS = Attachment::PICTURE_EXTS; + protected const VIDEO_EXTS = ['mp4', 'ogv', 'ogg', 'webm']; + protected const AUDIO_EXTS = ['mp3', 'flac', 'ogg', 'oga', 'wav', 'm4a', 'opus']; + + protected $mimeTypes; + protected const ALLOWED_MIME_PLACEHOLDERS = ['image/*', 'audio/*', 'video/*']; + protected $cache; + + public function __construct(MimeTypesInterface $mimeTypes, CacheInterface $cache) + { + $this->mimeTypes = $mimeTypes; + $this->cache = $cache; + } + + + /** + * Check if a filetype filter string is valid. + * @param string $filter The filter string that should be validated. + * @return bool Returns true, if the string is valid. + */ + public function validateFilterString(string $filter) : bool + { + $filter = trim($filter); + //An empty filter is valid (means no filter applied) + if ($filter === '') { + return true; + } + + $elements = explode(',', $filter); + //Check for each element if it is valid: + foreach ($elements as $element) { + $element = trim($element); + if (!preg_match('/^\.\w+$/', $element) // .ext is allowed + && !preg_match('/^[-\w.]+\/[-\w.]+/', $element) //Explicit MIME type is allowed + && !in_array($element, static::ALLOWED_MIME_PLACEHOLDERS, false)) { //image/* is allowed + return false; + } + } + + //If no element was invalid, the whole string is valid + return true; + } + + /** + * Normalize a filter string. All extensions are converted to lowercase, too much whitespaces are removed. + * The filter string is not validated. + * @param string $filter The filter string that should be normalized. + * @return string The normalized filter string + */ + public function normalizeFilterString(string $filter) : string + { + $filter = trim($filter); + //Replace other separators, with , so we can split it properly + $filter = str_replace(';', ',', $filter); + //Make everything lower case + $filter = strtolower($filter); + + $elements = explode(',', $filter); + //Check for each element if it is valid: + foreach ($elements as $key => &$element) { + $element = trim($element); + //Remove empty elements + if ($element === '') { + unset($elements[$key]); + } + + //Convert *.jpg to .jpg + if (strpos($element, '*.') === 0) { + $element = str_replace('*.', '.', $element); + } + + //Convert image to image/* + if ($element === 'image' || $element === 'image/') { + $element = 'image/*'; + } elseif ($element === 'video' || $element === 'video/') { + $element = 'video/*'; + } elseif ($element === 'audio' || $element === 'audio/') { + $element = 'audio/*'; + } elseif (!preg_match('/^[-\w.]+\/[-\w.*]+/', $element) && strpos($element, '.') !== 0) { + //Convert jpg to .jpg + $element = '.' . $element; + } + + } + + $elements = array_unique($elements); + + return implode($elements, ','); + } + + /** + * Get a list of all file extensions that matches the given filter string + * @param string $filter A valid filetype filter string. + * @return string[] An array of allowed extensions ['txt', 'csv', 'gif'] + */ + public function resolveFileExtensions(string $filter) : array + { + $filter = trim($filter); + + return $this->cache->get('filter_exts_' . md5($filter), function (ItemInterface $item) use ($filter) { + $elements = explode(',', $filter); + $extensions = []; + + foreach ($elements as $element) { + $element = trim($element); + if (strpos($element, '.') === 0) { + //We found an explicit specified file extension -> add it to list + $extensions[] = substr($element, 1); + } elseif ($element === 'image/*') { + $extensions = array_merge($extensions, static::IMAGE_EXTS); + } elseif ($element === 'audio/*') { + $extensions = array_merge($extensions, static::AUDIO_EXTS); + } elseif ($element === 'image/*') { + $extensions = array_merge($extensions, static::VIDEO_EXTS); + } elseif (preg_match('/^[-\w.]+\/[-\w.*]+/', $element)) { + $extensions = array_merge($extensions, $this->mimeTypes->getExtensions($element)); + } + } + return array_unique($extensions); + }); + } + + /** + * Check if the given extension matches the filter. + * @param string $filter The filter which should be used for checking. + * @param string $extension The extension that should be checked. + * @return bool Returns true, if the extension is allowed with the given filter. + */ + public function isExtensionAllowed(string $filter, string $extension) : bool + { + $extension = strtolower($extension); + return empty($filter) || in_array($extension, $this->resolveFileExtensions($filter), false); + } +} \ No newline at end of file diff --git a/src/Validator/Constraints/AllowedFileExtension.php b/src/Validator/Constraints/AllowedFileExtension.php new file mode 100644 index 00000000..22dd8b2c --- /dev/null +++ b/src/Validator/Constraints/AllowedFileExtension.php @@ -0,0 +1,44 @@ +filterTools = $filterTools; + } + + /** + * Checks if the passed value is valid. + * + * @param mixed $value The value that should be validated + * @param Constraint $constraint The constraint for the validation + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof AllowedFileExtension) { + throw new UnexpectedTypeException($constraint, AllowedFileExtension::class); + } + + if ($value instanceof UploadedFile) { + if ($this->context->getObject() instanceof Attachment) { + /** @var Attachment $attachment */ + $attachment = $this->context->getObject(); + } elseif ($this->context->getObject() instanceof FormInterface) { + $attachment = $this->context->getObject()->getParent()->getData(); + } else { + return; + } + + $attachment_type = $attachment->getAttachmentType(); + + //Only validate if the attachment type has specified an filetype filter: + if ($attachment_type === null || empty($attachment_type->getFiletypeFilter())) { + return; + } + + if (!$this->filterTools->isExtensionAllowed( + $attachment_type->getFiletypeFilter(), + $value->getClientOriginalExtension() + )) { + $this->context->buildViolation('validator.file_ext_not_allowed')->addViolation(); + } + + } + } +} \ No newline at end of file diff --git a/src/Validator/Constraints/NoLockout.php b/src/Validator/Constraints/NoLockout.php index 4273213b..121cf1aa 100644 --- a/src/Validator/Constraints/NoLockout.php +++ b/src/Validator/Constraints/NoLockout.php @@ -37,6 +37,7 @@ use Symfony\Component\Validator\Constraint; /** * This constraint restricts a user in that way that it can not lock itself out of the user system * @package App\Validator\Constraints + * @Annotation */ class NoLockout extends Constraint { diff --git a/src/Validator/Constraints/UrlOrBuiltin.php b/src/Validator/Constraints/UrlOrBuiltin.php index dcfcabc7..48068502 100644 --- a/src/Validator/Constraints/UrlOrBuiltin.php +++ b/src/Validator/Constraints/UrlOrBuiltin.php @@ -38,6 +38,7 @@ use Symfony\Component\Validator\Constraints\Url; /** * Constraints the field that way that the content is either a url or a path to a builtin ressource (like %FOOTPRINTS%) * @package App\Validator\Constraints + * @Annotation */ class UrlOrBuiltin extends Url { diff --git a/src/Validator/Constraints/ValidFileFilter.php b/src/Validator/Constraints/ValidFileFilter.php new file mode 100644 index 00000000..bcb5cd3c --- /dev/null +++ b/src/Validator/Constraints/ValidFileFilter.php @@ -0,0 +1,44 @@ +filterTools = $filterTools; + } + + /** + * Checks if the passed value is valid. + * + * @param mixed $value The value that should be validated + * @param Constraint $constraint The constraint for the validation + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof ValidFileFilter) { + throw new UnexpectedTypeException($constraint, ValidFileFilter::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_string($value)) { + // throw this exception if your validator cannot handle the passed type so that it can be marked as invalid + throw new UnexpectedValueException($value, 'string'); + } + + if (!$this->filterTools->validateFilterString($value)) { + $this->context->buildViolation('validator.file_type_filter.invalid') + ->addViolation(); + } + } +} \ No newline at end of file diff --git a/templates/AdminPages/AttachmentTypeAdmin.html.twig b/templates/AdminPages/AttachmentTypeAdmin.html.twig index 85ba9366..49c02070 100644 --- a/templates/AdminPages/AttachmentTypeAdmin.html.twig +++ b/templates/AdminPages/AttachmentTypeAdmin.html.twig @@ -2,4 +2,8 @@ {% block card_title %} {% trans %}attachment_type.caption{% endtrans %} +{% endblock %} + +{% block additional_controls %} + {{ form_row(form.filetype_filter) }} {% endblock %} \ No newline at end of file diff --git a/tests/Controller/AdminPages/AbstractAdminControllerTest.php b/tests/Controller/AdminPages/AbstractAdminControllerTest.php index 49661861..45e33c39 100644 --- a/tests/Controller/AdminPages/AbstractAdminControllerTest.php +++ b/tests/Controller/AdminPages/AbstractAdminControllerTest.php @@ -38,6 +38,10 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Security\Core\Security; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ abstract class AbstractAdminControllerTest extends WebTestCase { protected static $base_path = 'not_valid'; @@ -62,6 +66,7 @@ abstract class AbstractAdminControllerTest extends WebTestCase /** * @dataProvider readDataProvider + * @group slow * Tests if you can access the /new part which is used to list all entities. Checks if permissions are working */ public function testListEntries(string $user, bool $read) @@ -81,6 +86,7 @@ abstract class AbstractAdminControllerTest extends WebTestCase /** * @dataProvider readDataProvider + * @group slow * Tests if it possible to access an specific entity. Checks if permissions are working. */ public function testReadEntity(string $user, bool $read) @@ -110,6 +116,7 @@ abstract class AbstractAdminControllerTest extends WebTestCase /** * Tests if deleting an entity is working. + * @group slow * @dataProvider deleteDataProvider */ public function testDeleteEntity(string $user, bool $delete) diff --git a/tests/Controller/AdminPages/AttachmentTypeControllerTest.php b/tests/Controller/AdminPages/AttachmentTypeControllerTest.php index 092f79b8..7c0cbf56 100644 --- a/tests/Controller/AdminPages/AttachmentTypeControllerTest.php +++ b/tests/Controller/AdminPages/AttachmentTypeControllerTest.php @@ -34,6 +34,10 @@ namespace App\Tests\Controller\AdminPages; use App\Entity\Attachments\AttachmentType; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class AttachmentTypeControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/attachment_type'; diff --git a/tests/Controller/AdminPages/CategoryControllerTest.php b/tests/Controller/AdminPages/CategoryControllerTest.php index f32ac520..7f647669 100644 --- a/tests/Controller/AdminPages/CategoryControllerTest.php +++ b/tests/Controller/AdminPages/CategoryControllerTest.php @@ -33,6 +33,10 @@ namespace App\Tests\Controller\AdminPages; use App\Entity\Parts\Category; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class CategoryControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/category'; diff --git a/tests/Controller/AdminPages/DeviceControllerTest.php b/tests/Controller/AdminPages/DeviceControllerTest.php index 097d01b0..28d18024 100644 --- a/tests/Controller/AdminPages/DeviceControllerTest.php +++ b/tests/Controller/AdminPages/DeviceControllerTest.php @@ -35,6 +35,10 @@ namespace App\Tests\Controller\AdminPages; use App\Entity\Attachments\AttachmentType; use App\Entity\Devices\Device; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class DeviceControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/device'; diff --git a/tests/Controller/AdminPages/FootprintControllerTest.php b/tests/Controller/AdminPages/FootprintControllerTest.php index 62fd4e57..2bedbcbf 100644 --- a/tests/Controller/AdminPages/FootprintControllerTest.php +++ b/tests/Controller/AdminPages/FootprintControllerTest.php @@ -35,6 +35,10 @@ namespace App\Tests\Controller\AdminPages; use App\Entity\Attachments\AttachmentType; use App\Entity\Parts\Footprint; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class FootprintControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/footprint'; diff --git a/tests/Controller/AdminPages/ManufacturerControllerTest.php b/tests/Controller/AdminPages/ManufacturerControllerTest.php index ebc6625d..a2cb49e4 100644 --- a/tests/Controller/AdminPages/ManufacturerControllerTest.php +++ b/tests/Controller/AdminPages/ManufacturerControllerTest.php @@ -36,6 +36,10 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Devices\Device; use App\Entity\Parts\Manufacturer; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class ManufacturerControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/manufacturer'; diff --git a/tests/Controller/AdminPages/MeasurementUnitControllerTest.php b/tests/Controller/AdminPages/MeasurementUnitControllerTest.php index 782c5cbe..70e2b670 100644 --- a/tests/Controller/AdminPages/MeasurementUnitControllerTest.php +++ b/tests/Controller/AdminPages/MeasurementUnitControllerTest.php @@ -37,6 +37,10 @@ use App\Entity\Devices\Device; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class MeasurementUnitControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/measurement_unit'; diff --git a/tests/Controller/AdminPages/StorelocationControllerTest.php b/tests/Controller/AdminPages/StorelocationControllerTest.php index cef96339..b3584478 100644 --- a/tests/Controller/AdminPages/StorelocationControllerTest.php +++ b/tests/Controller/AdminPages/StorelocationControllerTest.php @@ -38,6 +38,10 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Storelocation; use Symfony\Component\HttpKernel\HttpCache\Store; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class StorelocationControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/store_location'; diff --git a/tests/Controller/AdminPages/SupplierControllerTest.php b/tests/Controller/AdminPages/SupplierControllerTest.php index fe908bf6..a28161f3 100644 --- a/tests/Controller/AdminPages/SupplierControllerTest.php +++ b/tests/Controller/AdminPages/SupplierControllerTest.php @@ -37,6 +37,10 @@ use App\Entity\Devices\Device; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Supplier; +/** + * @group slow + * @package App\Tests\Controller\AdminPages + */ class SupplierControllerTest extends AbstractAdminControllerTest { protected static $base_path = '/en' . '/supplier'; diff --git a/tests/Controller/RedirectControllerTest.php b/tests/Controller/RedirectControllerTest.php index 584b4273..61e09d5c 100644 --- a/tests/Controller/RedirectControllerTest.php +++ b/tests/Controller/RedirectControllerTest.php @@ -36,6 +36,10 @@ use Doctrine\ORM\EntityManagerInterface; use Proxies\__CG__\App\Entity\UserSystem\User; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +/** + * @group slow + * @package App\Tests\Controller + */ class RedirectControllerTest extends WebTestCase { protected $em; @@ -65,6 +69,7 @@ class RedirectControllerTest extends WebTestCase /** * Test if a certain request to an url will be redirected. * @dataProvider urlMatchDataProvider + * @group slow */ public function testUrlMatch($url, $expect_redirect) { @@ -95,6 +100,7 @@ class RedirectControllerTest extends WebTestCase /** * Test if the user is redirected to the localized version of a page, based on his settings. * @dataProvider urlAddLocaleDataProvider + * @group slow * @depends testUrlMatch * @param $user_locale * @param $input_path @@ -124,6 +130,7 @@ class RedirectControllerTest extends WebTestCase /** * Test if the user is redirected to password change page if he should do that * @depends testAddLocale + * @group slow * @testWith ["de"] * ["en"] */ diff --git a/tests/Services/Attachments/FileTypeFilterToolsTest.php b/tests/Services/Attachments/FileTypeFilterToolsTest.php new file mode 100644 index 00000000..38165835 --- /dev/null +++ b/tests/Services/Attachments/FileTypeFilterToolsTest.php @@ -0,0 +1,127 @@ +get(FileTypeFilterTools::class); + } + + public function validateDataProvider() : array + { + return [ + ['', true], //Empty string is valid + ['.jpeg,.png, .gif', true], //Only extensions are valid + ['image/*, video/*, .mp4, video/x-msvideo, application/vnd.amazon.ebook', true], + ['application/vnd.amazon.ebook, audio/opus', true], + + ['*.notvalid, .png', false], //No stars in extension + ['test.png', false], //No full filename + ['application/*', false], //Only certain placeholders are allowed + ['.png;.png,.jpg', false], //Wrong separator + ['.png .jpg .gif', false] + ]; + } + + public function normalizeDataProvider() : array + { + return [ + ['', ''], + ['.jpeg,.png,.gif', '.jpeg,.png,.gif'], + ['.jpeg, .png, .gif,', '.jpeg,.png,.gif'], + ['jpg, *.gif', '.jpg,.gif'], + ['video, image/', 'video/*,image/*'], + ['video/*', 'video/*'], + ['video/x-msvideo,.jpeg', 'video/x-msvideo,.jpeg'], + ['.video', '.video'], + //Remove duplicate entries + ['png, .gif, .png,', '.png,.gif'], + ]; + } + + public function extensionAllowedDataProvider() : array + { + return [ + ['', 'txt', true], + ['', 'everything_should_match', true], + + ['.jpg,.png', 'jpg', true], + ['.jpg,.png', 'png', true], + ['.jpg,.png', 'txt', false], + + ['image/*', 'jpeg', true], + ['image/*', 'png', true], + ['image/*', 'txt', false], + + ['application/pdf,.txt', 'pdf', true], + ['application/pdf,.txt', 'txt', true], + ['application/pdf,.txt', 'jpg', false], + ]; + } + + /** + * Test the validateFilterString method + * @dataProvider validateDataProvider + * @param string $filter + * @param bool $expected + */ + public function testValidateFilterString(string $filter, bool $expected) + { + $this->assertEquals($expected, self::$service->validateFilterString($filter)); + } + + /** + * @dataProvider normalizeDataProvider + * @param string $filter + * @param string $expected + */ + public function testNormalizeFilterString(string $filter, string $expected) + { + $this->assertEquals($expected, self::$service->normalizeFilterString($filter)); + } + + /** + * @dataProvider extensionAllowedDataProvider + */ + public function testIsExtensionAllowed(string $filter, string $extension, bool $expected) + { + $this->assertEquals($expected, self::$service->isExtensionAllowed($filter, $extension), $expected); + } +} \ No newline at end of file