diff --git a/tests/resources/testDocument.pdf b/tests/resources/testDocument.pdf new file mode 100644 index 000000000..08299237a Binary files /dev/null and b/tests/resources/testDocument.pdf differ diff --git a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php index aa55a5548..dcea64514 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/BusinessModel/CasesTest.php @@ -7,6 +7,7 @@ use G; use ProcessMaker\Model\Application; use ProcessMaker\Model\Delegation; use ProcessMaker\Model\Documents; +use ProcessMaker\Model\User; use RBAC; use Tests\TestCase; @@ -17,6 +18,21 @@ use Tests\TestCase; */ class CasesTest extends TestCase { + + /** + * Set up method. + */ + public function setUp() + { + parent::setUp(); + Delegation::truncate(); + Documents::truncate(); + Application::truncate(); + User::where('USR_ID', '=', 1) + ->where('USR_ID', '=', 2) + ->delete(); + } + /** * This checks the delete case * @@ -28,11 +44,11 @@ class CasesTest extends TestCase { // Set the RBAC global $RBAC; - $_SESSION['USER_LOGGED'] = '00000000000000000000000000000002'; - $RBAC = RBAC::getSingleton(PATH_DATA, session_id()); + $_SESSION['USER_LOGGED'] = G::generateUniqueID(); + $RBAC = RBAC::getSingleton(); $RBAC->initRBAC(); - $application = factory(Application::class)->create(); + $application = factory(Application::class)->create(['APP_INIT_USER' => G::generateUniqueID()]); // Tried to delete case $case = new Cases(); $case->deleteCase($application->APP_UID, $_SESSION['USER_LOGGED']); @@ -50,7 +66,7 @@ class CasesTest extends TestCase // Set the RBAC global $RBAC; $_SESSION['USER_LOGGED'] = '00000000000000000000000000000001'; - $RBAC = RBAC::getSingleton(PATH_DATA, session_id()); + $RBAC = RBAC::getSingleton(); $RBAC->initRBAC(); $application = factory(Application::class)->create(['APP_STATUS' => 'TO_DO']); @@ -70,38 +86,17 @@ class CasesTest extends TestCase { // Set the RBAC global $RBAC; - $_SESSION['USER_LOGGED'] = '00000000000000000000000000000001'; - $RBAC = RBAC::getSingleton(PATH_DATA, session_id()); + $_SESSION['USER_LOGGED'] = G::generateUniqueID(); + $RBAC = RBAC::getSingleton(); $RBAC->initRBAC(); - $application = factory(Application::class)->create(['APP_INIT_USER' => '00000000000000000000000000000002']); + $application = factory(Application::class)->create(['APP_INIT_USER' => G::generateUniqueID()]); // Tried to delete case $case = new Cases(); $case->deleteCase($application->APP_UID, $_SESSION['USER_LOGGED']); } /** - * Review the upload file related to the case notes, an return an exception when the array is empty - * - * @covers \ProcessMaker\BusinessModel\Cases::uploadFilesInCaseNotes() - * - * @test - * @expectedException Exception - */ - public function it_return_exception_in_upload_files_related_case_note() - { - $application = factory(Application::class)->create(); - factory(Delegation::class)->states('foreign_keys')->create([ - 'APP_NUMBER' => $application->APP_NUMBER, - 'APP_UID' => $application->APP_UID - ]); - // Upload the file - $case = new Cases(); - // Return an exception because the files does not exist - $case->uploadFilesInCaseNotes('00000000000000000000000000000001', $application->APP_UID, $filesReferences = []); - } - - /** * Review the upload file related to the case notes * * @covers \ProcessMaker\BusinessModel\Cases::uploadFilesInCaseNotes() diff --git a/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php b/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php index 21cb3b094..d4b8f2edf 100644 --- a/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php +++ b/tests/unit/workflow/engine/src/ProcessMaker/Validation/ValidationUploadedFilesTest.php @@ -112,6 +112,43 @@ class ValidationUploadedFilesTest extends TestCase $this->assertEquals(0, $result->getStatus()); } + /** + * This test verify validation rules for files post in cases notes. + * @test + * @covers ::runRulesForPostFilesOfNote + */ + public function it_should_test_run_rules_for_post_files_of_note() + { + //assert for file has not exist + $file = [ + 'filename' => 'testDocument.pdf', + 'path' => "testDocument.pdf" + ]; + $validation = new ValidationUploadedFiles(); + $result = $validation->runRulesForPostFilesOfNote($file); + $this->assertTrue($result->fails()); + + //assert for file has not valid extension + $file = [ + 'filename' => 'projectData.json', + 'path' => PATH_TRUNK . "tests/resources/projectData.json" + ]; + $validation = new ValidationUploadedFiles(); + $result = $validation->runRulesForPostFilesOfNote($file); + $this->assertTrue($result->fails()); + + //assert the file exists and has valid extension + $file = [ + 'filename' => 'testDocument.pdf', + 'path' => PATH_TRUNK . "tests/resources/testDocument.pdf" + ]; + $validation = new ValidationUploadedFiles(); + $result = $validation->runRulesForPostFilesOfNote($file); + $this->assertFalse($result->fails()); + $this->assertEmpty($result->getMessage()); + $this->assertEquals(0, $result->getStatus()); + } + /** * It deletes the images created */ @@ -128,4 +165,4 @@ class ValidationUploadedFilesTest extends TestCase unlink(PATH_DATA . '1.PnG'); } } -} \ No newline at end of file +} diff --git a/workflow/engine/content/translations/english/processmaker.en.po b/workflow/engine/content/translations/english/processmaker.en.po index e0d1b1379..3817ab047 100644 --- a/workflow/engine/content/translations/english/processmaker.en.po +++ b/workflow/engine/content/translations/english/processmaker.en.po @@ -25307,6 +25307,12 @@ msgstr "Error: The application {0} is not canceled." msgid "The default configuration was not defined" msgstr "The default configuration was not defined" +# TRANSLATION +# LABEL/ID_THE_FILE_COULDNT_BE_UPLOADED +#: LABEL/ID_THE_FILE_COULDNT_BE_UPLOADED +msgid "The file couldn’t be uploaded please review the allowed files or contact your System Administrator." +msgstr "The file couldn’t be uploaded please review the allowed files or contact your System Administrator." + # TRANSLATION # LABEL/ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED #: LABEL/ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED @@ -27707,6 +27713,12 @@ msgstr "Yesterday" msgid "[LABEL/ID_YES_VALUE] Yes" msgstr "Yes" +# TRANSLATION +# LABEL/ID_YOUR_FILE_HAS_EXCEEDED +#: LABEL/ID_YOUR_FILE_HAS_EXCEEDED +msgid "Your file has exceeded the file maximum size that is {0}." +msgstr "Your file has exceeded the file maximum size that is {0}." + # TRANSLATION # LABEL/ID_YOUR_IMAGE_HAS_BEEN_SUCCESSFULLY #: LABEL/ID_YOUR_IMAGE_HAS_BEEN_SUCCESSFULLY @@ -27761,6 +27773,12 @@ msgstr "You do not select any user to import" msgid "you have an error" msgstr "you have an error" +# TRANSLATION +# LABEL/ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION +#: LABEL/ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION +msgid "You uploaded an unsupported file extension, please review the permitted files uploaded in the wiki of ProcessMaker for the cases notes." +msgstr "You uploaded an unsupported file extension, please review the permitted files uploaded in the wiki of ProcessMaker for the cases notes." + # TRANSLATION # LABEL/ID_ZIP_CODE #: LABEL/ID_ZIP_CODE diff --git a/workflow/engine/controllers/appProxy.php b/workflow/engine/controllers/appProxy.php index ab30a6028..f105d3b71 100644 --- a/workflow/engine/controllers/appProxy.php +++ b/workflow/engine/controllers/appProxy.php @@ -10,6 +10,7 @@ */ use ProcessMaker\BusinessModel\Cases as BmCases; +use ProcessMaker\Exception\CaseNoteUploadFile; use ProcessMaker\Model\AppNotes as Notes; use ProcessMaker\Model\Documents; use ProcessMaker\Util\DateTime; @@ -165,6 +166,11 @@ class AppProxy extends HttpProxyController try { $sendMail = intval($httpData->swSendMail); $response = $cases->addNote($appUid, $usrUid, $noteContent, $sendMail); + } catch (CaseNoteUploadFile $e) { + $response = new stdclass(); + $response->success = 'success'; + $response->message = $e->getMessage(); + die(G::json_encode($response)); } catch (Exception $error) { $response = new stdclass(); $response->success = 'success'; diff --git a/workflow/engine/data/mysql/insert.sql b/workflow/engine/data/mysql/insert.sql index 0a07360af..c34c84ad4 100644 --- a/workflow/engine/data/mysql/insert.sql +++ b/workflow/engine/data/mysql/insert.sql @@ -61109,6 +61109,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_THERE_PROBLEM_SENDING_EMAIL','en','There was a problem sending the email to','2016-04-08') , ( 'LABEL','ID_THE_APPLICATION_IS_NOT_CANCELED','en','Error: The application {0} is not canceled.','2016-06-15') , ( 'LABEL','ID_THE_DEFAULT_CONFIGURATION','en','The default configuration was not defined','2016-11-16') , +( 'LABEL','ID_THE_FILE_COULDNT_BE_UPLOADED','en','The file couldn’t be uploaded please review the allowed files or contact your System Administrator.','2020-06-12') , ( 'LABEL','ID_THE_FILE_SIZE_IS_BIGGER_THAN_THE_MAXIMUM_ALLOWED','en','The file size is bigger than the maximum allowed, the maximum size allowed is {0} Mbytes.','2019-02-26') , ( 'LABEL','ID_THE_MAXIMUM_VALUE_OF_THIS_FIELD_IS','en','The maximum value of this field is {0}.','2019-02-26') , ( 'LABEL','ID_THE_MIMETYPE_EXTENSION_ERROR','en','The mime type does not correspond to the permitted extension, please verify your file.','2018-10-02') , @@ -61519,6 +61520,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_YES','en','Yes','2014-01-15') , ( 'LABEL','ID_YESTERDAY','en','Yesterday','2014-01-15') , ( 'LABEL','ID_YES_VALUE','en','Yes','2014-01-15') , +( 'LABEL','ID_YOUR_FILE_HAS_EXCEEDED','en','Your file has exceeded the file maximum size that is 10MB.','2020-06-12') , ( 'LABEL','ID_YOUR_IMAGE_HAS_BEEN_SUCCESSFULLY','en','Your image has been successfully uploaded','2014-01-15') , ( 'LABEL','ID_YOUR_LICENSE','en','Your license','2014-09-18') , ( 'LABEL','ID_YOUR_PASSWORD_IS','en','Your password is','2014-01-15') , @@ -61528,6 +61530,7 @@ INSERT INTO TRANSLATION (TRN_CATEGORY,TRN_ID,TRN_LANG,TRN_VALUE,TRN_UPDATE_DATE ( 'LABEL','ID_YOU_DO_NOT_HAVE_PERMISSION','en','Error: You do not have permission.','2016-06-15') , ( 'LABEL','ID_YOU_DO_NOT_SELECT_ANY_USER_TO_IMPORT','en','You do not select any user to import','2015-09-15') , ( 'LABEL','ID_YOU_HAVE_ERROR','en','you have an error','2014-01-15') , +( 'LABEL','ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION','en','You uploaded an unsupported file extension, please review the permitted files uploaded in the wiki of ProcessMaker for the cases notes.','2020-06-12') , ( 'LABEL','ID_ZIP_CODE','en','Zip Code','2014-01-15') , ( 'LABEL','IMAGE_DETAIL','en','Image detail','2014-01-15') , ( 'LABEL','IMPORT_LANGUAGE_ERR_NO_WRITABLE','en','The XML forms directory is not writable','2014-01-15') , diff --git a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php index 571ab3b66..1182c5177 100644 --- a/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php +++ b/workflow/engine/src/ProcessMaker/BusinessModel/Cases.php @@ -41,6 +41,7 @@ use ProcessMaker\BusinessModel\Task as BmTask; use ProcessMaker\BusinessModel\User as BmUser; use ProcessMaker\Core\System; use ProcessMaker\Exception\UploadException; +use ProcessMaker\Exception\CaseNoteUploadFile; use ProcessMaker\Model\Application as ModelApplication; use ProcessMaker\Model\AppNotes as Notes; use ProcessMaker\Model\Delegation; @@ -49,6 +50,7 @@ use ProcessMaker\Plugins\PluginRegistry; use ProcessMaker\Services\OAuth2\Server; use ProcessMaker\Util\DateTime as UtilDateTime; use ProcessMaker\Validation\ExceptionRestApi; +use ProcessMaker\Validation\ValidationUploadedFiles; use ProcessMaker\Validation\Validator as FileValidator; use ProcessPeer; use ProcessUser; @@ -3960,6 +3962,21 @@ class Cases } } + //rules validation + foreach ($files as $key => $value) { + $entry = [ + "filename" => $value['name'], + "path" => $value['tmp_name'] + ]; + $validator = ValidationUploadedFiles::getValidationUploadedFiles() + ->runRulesForPostFilesOfNote($entry); + if ($validator->fails()) { + Notes::where('NOTE_ID', '=', $noteId)->delete(); + $messageError = G::LoadTranslation('ID_THE_FILE_COULDNT_BE_UPLOADED'); + throw new CaseNoteUploadFile($messageError . ' ' . $validator->getMessage()); + } + } + // Get the delIndex related to the case $cases = new ClassesCases(); $delIndex = $cases->getCurrentDelegation($appUid); @@ -4007,8 +4024,6 @@ class Cases throw new UploadException($fileName['error']); } } - } else { - throw new Exception(G::LoadTranslation('ID_ERROR_UPLOAD_FILE_CONTACT_ADMINISTRATOR')); } return $response; diff --git a/workflow/engine/src/ProcessMaker/Exception/CaseNoteUploadFile.php b/workflow/engine/src/ProcessMaker/Exception/CaseNoteUploadFile.php new file mode 100644 index 000000000..246a1ea66 --- /dev/null +++ b/workflow/engine/src/ProcessMaker/Exception/CaseNoteUploadFile.php @@ -0,0 +1,21 @@ +validate(); } + /** + * Check if the loaded files comply with the validation rules, add here if you + * want more validation rules. + * Accept per argument an array or object that contains a "filename" and "path" values. + * The rules are verified in the order in which they have been added. + * + * @param array|object $file + * @return Validator + */ + public function runRulesForPostFilesOfNote($file) + { + $validator = new Validator(); + + //rule: file exists + $rule = $validator->addRule(); + $rule->validate($file, function($file) use($rule) { + $path = isset($file->path) ? $file->path : ""; + $filesystem = new Filesystem(); + if (!$filesystem->exists($path)) { + $rule->message(G::LoadTranslation('ID_NOT_EXISTS_FILE')); + return self::INVALID; + } + return self::VALID; + }) + ->status(400) + ->log(function($rule) { + /** + * Levels supported by MonologProvider is: + * 100 "DEBUG" + * 200 "INFO" + * 250 "NOTICE" + * 300 "WARNING" + * 400 "ERROR" + * 500 "CRITICAL" + * 550 "ALERT" + * 600 "EMERGENCY" + */ + Bootstrap::registerMonologPhpUploadExecution('phpUpload', $rule->getStatus(), $rule->getMessage(), $rule->getData()->filename); + }); + + //rule: extensions + $rule = $validator->addRule(); + $rule->validate($file, function($file) use($rule) { + $filesystem = new Filesystem(); + $extension = strtolower($filesystem->extension($file->filename)); + $extensions = [ + 'pdf', 'gif', 'jpg', 'png', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'mp4', 'mpv', 'mpeg', 'mpg', 'mov' + ]; + if (!in_array($extension, $extensions)) { + $rule->message(G::LoadTranslation('ID_YOU_UPLOADED_AN_UNSUPPORTED_FILE_EXTENSION')); + return self::INVALID; + } + return self::VALID; + }) + ->status(400) + ->log(function($rule) { + /** + * Levels supported by MonologProvider is: + * 100 "DEBUG" + * 200 "INFO" + * 250 "NOTICE" + * 300 "WARNING" + * 400 "ERROR" + * 500 "CRITICAL" + * 550 "ALERT" + * 600 "EMERGENCY" + */ + Bootstrap::registerMonologPhpUploadExecution('phpUpload', $rule->getStatus(), $rule->getMessage(), $rule->getData()->filename); + }); + + //rule: file size + $rule = $validator->addRule(); + $rule->validate($file, function($file) use($rule) { + $path = isset($file->path) ? $file->path : ""; + $filesystem = new Filesystem(); + $limitSize = '10M'; + $size = $filesystem->size($path); + $phpShorthandByte = new PhpShorthandByte(); + $postMaxSizeBytes = $phpShorthandByte->valueToBytes($limitSize); + if ($size > $postMaxSizeBytes) { + $rule->message(G::LoadTranslation('ID_YOUR_FILE_HAS_EXCEEDED', [$limitSize])); + return self::INVALID; + } + return self::VALID; + }) + ->status(400) + ->log(function($rule) { + /** + * Levels supported by MonologProvider is: + * 100 "DEBUG" + * 200 "INFO" + * 250 "NOTICE" + * 300 "WARNING" + * 400 "ERROR" + * 500 "CRITICAL" + * 550 "ALERT" + * 600 "EMERGENCY" + */ + Bootstrap::registerMonologPhpUploadExecution('phpUpload', $rule->getStatus(), $rule->getMessage(), $rule->getData()->filename); + }); + + return $validator->validate(); + } + /** * Get the first error and call the argument function. *