From 92aded30161e31db5cd0b1a835735f53f9f0dc46 Mon Sep 17 00:00:00 2001 From: Sabina Talipova Date: Fri, 13 May 2022 16:25:51 +1200 Subject: [PATCH] Protect uploads if folder for EditableFileField was removed --- code/Control/UserDefinedFormController.php | 47 +++++++++++++------ .../EditableFormField/EditableFileField.php | 11 +++++ .../Control/UserDefinedFormControllerTest.php | 44 +++++++++++++++++ tests/php/Model/EditableFormFieldTest.php | 45 ++++++++++++++++++ tests/php/Model/EditableFormFieldTest.yml | 15 ++++++ tests/php/UserFormsTest.yml | 9 ++++ 6 files changed, 156 insertions(+), 15 deletions(-) diff --git a/code/Control/UserDefinedFormController.php b/code/Control/UserDefinedFormController.php index ef11486..4a1d8a8 100644 --- a/code/Control/UserDefinedFormController.php +++ b/code/Control/UserDefinedFormController.php @@ -28,6 +28,7 @@ use SilverStripe\UserForms\Model\EditableFormField\EditableFileField; use SilverStripe\UserForms\Model\Submission\SubmittedForm; use SilverStripe\UserForms\Model\Submission\SubmittedFileField; use SilverStripe\UserForms\Model\UserDefinedForm; +use SilverStripe\Versioned\Versioned; use SilverStripe\View\ArrayData; use SilverStripe\View\Requirements; use SilverStripe\View\SSViewer; @@ -53,6 +54,8 @@ class UserDefinedFormController extends PageController /** @var string The name of the folder where form submissions will be placed by default */ private static $form_submissions_folder = 'Form-submissions'; + private static string $file_upload_stage = Versioned::DRAFT; + protected function init() { parent::init(); @@ -262,25 +265,39 @@ JS if (!empty($data[$field->Name])) { if (in_array(EditableFileField::class, $field->getClassAncestry() ?? [])) { if (!empty($_FILES[$field->Name]['name'])) { - $foldername = $field->getFormField()->getFolderName(); + if (!$field->getFolderExists()) { + $field->createProtectedFolder(); + } + + $file = Versioned::withVersionedMode(function () use ($field, $form) { + $stage = Injector::inst()->get(self::class)->config()->get('file_upload_stage'); + Versioned::set_stage($stage); - // create the file from post data - $upload = Upload::create(); - try { - $upload->loadIntoFile($_FILES[$field->Name], null, $foldername); - } catch (ValidationException $e) { - $validationResult = $e->getResult(); - foreach ($validationResult->getMessages() as $message) { - $form->sessionMessage($message['message'], ValidationResult::TYPE_ERROR); + $foldername = $field->getFormField()->getFolderName(); + // create the file from post data + $upload = Upload::create(); + try { + $upload->loadIntoFile($_FILES[$field->Name], null, $foldername); + } catch (ValidationException $e) { + $validationResult = $e->getResult(); + foreach ($validationResult->getMessages() as $message) { + $form->sessionMessage($message['message'], ValidationResult::TYPE_ERROR); + } + Controller::curr()->redirectBack(); + return null; } - Controller::curr()->redirectBack(); + /** @var AssetContainer|File $file */ + $file = $upload->getFile(); + $file->ShowInSearch = 0; + $file->UserFormUpload = UserFormFileExtension::USER_FORM_UPLOAD_TRUE; + $file->write(); + + return $file; + }); + + if (is_null($file)) { return; } - /** @var AssetContainer|File $file */ - $file = $upload->getFile(); - $file->ShowInSearch = 0; - $file->UserFormUpload = UserFormFileExtension::USER_FORM_UPLOAD_TRUE; - $file->write(); // generate image thumbnail to show in asset-admin // you can run userforms without asset-admin, so need to ensure asset-admin is installed diff --git a/code/Model/EditableFormField/EditableFileField.php b/code/Model/EditableFormField/EditableFileField.php index 2c8b625..2c88870 100755 --- a/code/Model/EditableFormField/EditableFileField.php +++ b/code/Model/EditableFormField/EditableFileField.php @@ -194,7 +194,18 @@ class EditableFileField extends EditableFormField return $result; } + public function getFolderExists(): bool + { + return $this->Folder()->ID !== 0; + } + public function createProtectedFolder(): void + { + $folderName = sprintf('page-%d/upload-field-%d', $this->ParentID, $this->ID); + $folder = UserDefinedFormAdmin::getFormSubmissionFolder($folderName); + $this->FolderID = $folder->ID; + $this->write(); + } public function getFormField() { diff --git a/tests/php/Control/UserDefinedFormControllerTest.php b/tests/php/Control/UserDefinedFormControllerTest.php index f0acfbe..4e1e716 100644 --- a/tests/php/Control/UserDefinedFormControllerTest.php +++ b/tests/php/Control/UserDefinedFormControllerTest.php @@ -24,6 +24,7 @@ use SilverStripe\UserForms\Model\EditableFormField\EditableTextField; use SilverStripe\UserForms\Model\Recipient\EmailRecipient; use SilverStripe\UserForms\Model\Submission\SubmittedFormField; use SilverStripe\UserForms\Model\UserDefinedForm; +use SilverStripe\Versioned\Versioned; use SilverStripe\View\ArrayData; use SilverStripe\View\SSViewer; use function filesize; @@ -467,4 +468,47 @@ class UserDefinedFormControllerTest extends FunctionalTest $nodata = $this->findEmail('nodata@example.com', 'no-reply@example.com', 'Email Subject'); $this->assertEmpty($nodata['AttachedFiles'], 'Recipients with HideFormData do not receive attachment'); } + + public function testMissingFolderCreated() + { + Config::modify()->set(Upload_Validator::class, 'use_is_uploaded_file', false); + $userForm = $this->setupFormFrontend('upload-form-without-folder'); + $controller = UserDefinedFormController::create($userForm); + $field = $this->objFromFixture(EditableFileField::class, 'file-field-3'); + + $path = realpath(__DIR__ . '/fixtures/testfile.jpg'); + $data = [ + $field->Name => [ + 'name' => 'testfile.jpg', + 'type' => 'image/jpeg', + 'tmp_name' => $path, + 'error' => 0, + 'size' => filesize($path ?? ''), + ] + ]; + $_FILES[$field->Name] = $data[$field->Name]; + + $controller->getRequest()->setSession(new Session([])); + + $folderExistBefore = $field->getFolderExists(); + $stageBefore = Versioned::get_stage(); + + $controller->process($data, $controller->Form()); + + $field = EditableFileField::get_by_id($field->ID); + $filter = [ + 'ParentID' => $field->Folder()->ID, + 'Name' => 'testfile.jpg', + ]; + $fileDraftCount = Versioned::get_by_stage(File::class, Versioned::DRAFT)->filter($filter)->count(); + $fileLiveCount = Versioned::get_by_stage(File::class, Versioned::LIVE)->filter($filter)->count(); + + $folderExistAfter = $field->getFolderExists(); + + $this->assertFalse($folderExistBefore); + $this->assertTrue($folderExistAfter); + $this->assertEquals($stageBefore, Versioned::get_stage()); + $this->assertEquals(1, $fileDraftCount); + $this->assertEquals(0, $fileLiveCount); + } } diff --git a/tests/php/Model/EditableFormFieldTest.php b/tests/php/Model/EditableFormFieldTest.php index 7bda250..8e2eb93 100644 --- a/tests/php/Model/EditableFormFieldTest.php +++ b/tests/php/Model/EditableFormFieldTest.php @@ -168,6 +168,51 @@ class EditableFormFieldTest extends FunctionalTest $this->assertNotContains('jpg', $formField->getValidator()->getAllowedExtensions()); } + /** + * Test that if folder is not set or folder was removed, + * then getFormField() saves file in protected folder + */ + public function testCreateProtectedFolder() + { + $fileField1 = $this->objFromFixture(EditableFileField::class, 'file-field-without-folder'); + $fileField2 = $this->objFromFixture(EditableFileField::class, 'file-field-with-folder'); + + $fileField1->createProtectedFolder(); + + $formField1 = $fileField1->getFormField(); + $formField2 = $fileField2->getFormField(); + + $canViewParent1 = $fileField1->Folder()->Parent()->Parent()->CanViewType; + $canViewParent2 = $fileField2->Folder()->Parent()->CanViewType; + + $this->assertEquals('OnlyTheseUsers', $canViewParent1); + $this->assertEquals('Inherit', $canViewParent2); + + $this->assertTrue( + (bool) preg_match( + sprintf( + '/^Form-submissions\/page-%d\/upload-field-%d/', + $fileField1->ParentID, + $fileField1->ID + ), + $formField1->folderName, + ) + ); + $this->assertEquals('folder1/folder1-1/', $formField2->folderName); + } + + /** + * Verify that folder is related to a field exist + */ + public function testGetFolderExists() + { + $fileField1 = $this->objFromFixture(EditableFileField::class, 'file-field-without-folder'); + $fileField2 = $this->objFromFixture(EditableFileField::class, 'file-field-with-folder'); + + $this->assertFalse($fileField1->getFolderExists()); + $this->assertTrue($fileField2->getFolderExists()); + } + /** * Verify that unique names are automatically generated for each formfield */ diff --git a/tests/php/Model/EditableFormFieldTest.yml b/tests/php/Model/EditableFormFieldTest.yml index 05e3795..8624e7d 100644 --- a/tests/php/Model/EditableFormFieldTest.yml +++ b/tests/php/Model/EditableFormFieldTest.yml @@ -2,6 +2,13 @@ SilverStripe\Security\Group: admin: Title: Administrators +SilverStripe\Assets\Folder: + user-form-folder-parent: + Title: folder1 + user-form-folder-child: + Title: folder1-1 + Parent: =>SilverStripe\Assets\Folder.user-form-folder-parent + SilverStripe\UserForms\Model\EditableFormField\EditableTextField: basic-text: Name: basic_text_name @@ -289,6 +296,14 @@ SilverStripe\UserForms\Model\EditableFormField\EditableFileField: file-field: Name: file-uploader Title: Set file + file-field-without-folder: + Name: file-uploader-without-folder + Title: Set file + FolderID: 0 + file-field-with-folder: + Name: file-uploader-with-folder + Title: Set file + FolderID: =>SilverStripe\Assets\Folder.user-form-folder-child SilverStripe\UserForms\Model\UserDefinedForm: basic-form-page: diff --git a/tests/php/UserFormsTest.yml b/tests/php/UserFormsTest.yml index a558ab6..f1768e9 100644 --- a/tests/php/UserFormsTest.yml +++ b/tests/php/UserFormsTest.yml @@ -275,6 +275,10 @@ SilverStripe\UserForms\Model\EditableFormField\EditableFileField: file-field-2: Name: FileUploadField Title: File Upload Field + file-field-3: + Name: FileUploadField + Title: File Upload Field Without Folder + Folder: '' SilverStripe\UserForms\Model\EditableFormField\EditableFieldGroupEnd: group1end: @@ -495,6 +499,11 @@ SilverStripe\UserForms\Model\UserDefinedForm: - =>SilverStripe\UserForms\Model\Recipient\EmailRecipient.upload-recipient - =>SilverStripe\UserForms\Model\Recipient\EmailRecipient.upload-no-data + upload-form-without-folder: + Title: 'Form with upload field without folder' + Fields: + - =>SilverStripe\UserForms\Model\EditableFormField\EditableFileField.file-field-3 + SilverStripe\UserForms\Model\EditableCustomRule: rule1: Display: Show