diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index f14ed9f78..2238d9a48 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -2921,7 +2921,7 @@ function spbc_scanner_oscron_count_found() */ function spbc_scanner_oscron_get_scanned() { - return OSCronModel::getTasksFromStorage(); + return OSCronModel::getTasksFromStorageAsArray(); } /** diff --git a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronController.php b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronController.php index f49e49837..d22344ba2 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronController.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronController.php @@ -16,7 +16,7 @@ private static function updateTask($uid, $status) $line_to_change->setStatus($status); $result = OSCronModel::updateTaskById($uid, $line_to_change); if (false === $result) { - return $result; + return false; } return OSCronModel::rewriteCronTabFile(); } @@ -31,7 +31,7 @@ public static function approveTask($uid) } /** * @param $uid - * @return false|int + * @return bool * @throws \Exception */ public static function disableTask($uid) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronLocale.php b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronLocale.php new file mode 100644 index 000000000..eda6cf230 --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronLocale.php @@ -0,0 +1,43 @@ +task_status_approved = __('Approved', 'security-malware-firewall'); + $this->task_status_disabled = __('Disabled', 'security-malware-firewall'); + $this->task_status_danger = __('Danger', 'security-malware-firewall'); + $this->no_windows_support = __('Windows OS cron handling is not supported.', 'security-malware-firewall'); + $this->no_difference = __('No difference found since last check.', 'security-malware-firewall'); + $this->error_write_cron_storage = __('Cannot write cron to storage', 'security-malware-firewall'); + $this->error_write_cron_env = __('Cannot write cron to environment', 'security-malware-firewall'); + $this->error_load_cron_storage = __('Cannot load cron content from storage', 'security-malware-firewall'); + $this->error_load_cron_env = __('No crontab file found in the server environment.', 'security-malware-firewall'); + $this->error_load_tasks_storage = __('Cannot load tasks from storage', 'security-malware-firewall'); + $this->error_invalid_arg = __('invalid argument', 'security-malware-firewall'); + $this->error_missing_property = __('property is no set', 'security-malware-firewall'); + $this->error_invalid_cron_expression = __('Invalid cron expression', 'security-malware-firewall'); + //$this->var = __('', 'security-malware-firewall'); + } +} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronModel.php b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronModel.php index b36b2dc03..9534ab660 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronModel.php @@ -14,26 +14,35 @@ class OSCronModel public static function run() { try { + if (static::isWindows()) { + throw new \Exception(OSCronLocale::getInstance()->no_windows_support); + } + $new_cron_file = static::getEnvCrontab(); - if (static::loadCrontabFile() === $new_cron_file) { - throw new \Exception(__('No difference found since last check.', 'security-malware-firewall')); + if (static::loadCronFileFromStorage() === $new_cron_file) { + throw new \Exception(OSCronLocale::getInstance()->no_difference); } - static::saveCrontabFile($new_cron_file); + static::saveCronFileToStorage($new_cron_file); $tasks = static::parseTasks($new_cron_file); static::saveTasksToStorage($tasks); } catch (\Exception $error) { - return (string)$error; + return $error->getMessage(); } return true; } + public static function isWindows() + { + return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + } + /** * @param $cron_file * @return OSCronTask[] * @throws \Exception */ - private static function parseTasks($cron_file) + public static function parseTasks($cron_file) { $result = explode("\n", $cron_file); @@ -72,34 +81,45 @@ public static function saveTasksToStorage($tasks) } /** - * @return OSCronTask[]|array + * @return OSCronTask[] */ - public static function getTasksFromStorage($as_array = true) + public static function getTasksFromStorage() { - $tasks = get_option('spbc_oscron_result', []); + $tasks = get_option(static::$tasks_storage_name, []); if (is_null($tasks) || false === $tasks) { return array(); } - if ($as_array) { - $result_array = array(); - foreach ($tasks as $task) { + return $tasks; + } + + /** + * @return array + */ + public static function getTasksFromStorageAsArray() + { + global $spbc; + $tasks = static::getTasksFromStorage(); + $result_array = array(); + foreach ($tasks as $task) { + if ($task instanceof OSCronTask) { $result_array[] = $task->getArray(); + } else { + $spbc->error_add('', OSCronLocale::getInstance()->error_load_cron_storage); } - return $result_array; } - - return $tasks; + return $result_array; } /** * @param string $uid * @return OSCronTask|false + * @throws \Exception */ public static function getTaskById($uid) { - $tasks = static::getTasksFromStorage(false); + $tasks = static::getTasksFromStorage(); foreach ($tasks as $task) { if ($task->id === $uid) { return $task; @@ -117,7 +137,7 @@ public static function getTaskById($uid) public static function updateTaskById($uid, $new_task) { $new_task->validate(); - $tasks = static::getTasksFromStorage(false); + $tasks = static::getTasksFromStorage(); foreach ($tasks as $key => $task) { if ($task->id === $uid) { $tasks[$key] = $new_task; @@ -134,8 +154,8 @@ public static function updateTaskById($uid, $new_task) */ public static function rewriteCronTabFile() { - $tasks = static::getTasksFromStorage(false); - $new_cron_file = static::loadCrontabFile(); + $tasks = static::getTasksFromStorage(); + $new_cron_file = static::loadCronFileFromStorage(); foreach ($tasks as $task) { $the_word = $task->repeats . ' ' . $task->command; if ($task->status === 'disabled') { @@ -145,8 +165,14 @@ public static function rewriteCronTabFile() $new_cron_file = str_replace(static::$spbc_marker, '', $new_cron_file); } } - static::saveCrontabFile($new_cron_file); + static::saveCronFileToStorage($new_cron_file); + if ($new_cron_file !== static::loadCronFileFromStorage()) { + throw new \Exception(OSCronLocale::getInstance()->error_write_cron_storage); + } static::saveEnvCronTab($new_cron_file); + if ($new_cron_file !== static::getEnvCrontab()) { + throw new \Exception(OSCronLocale::getInstance()->error_write_cron_env); + } return true; } @@ -154,7 +180,7 @@ public static function rewriteCronTabFile() * @param $cron_file * @return void */ - public static function saveCrontabFile($cron_file) + public static function saveCronFileToStorage($cron_file) { update_option(static::$file_storage_name, @base64_encode($cron_file)); } @@ -163,16 +189,16 @@ public static function saveCrontabFile($cron_file) * @return string * @throws \Exception */ - public static function loadCrontabFile() + public static function loadCronFileFromStorage() { $result = get_option(static::$file_storage_name); - if ($result) { + if (false !== $result) { $result = @base64_decode($result); - if ($result) { + if (false !== $result) { return $result; } } - throw new \Exception(__('Cannot load crontab file from storage', 'security-malware-firewall')); + throw new \Exception(OSCronLocale::getInstance()->error_load_cron_storage); } /** @@ -190,7 +216,7 @@ private static function getEnvCrontab() //5 * * * * bash //#not a valid string'; if (empty($cron_file)) { - throw new \Exception(__('No crontab file found in the server environment.', 'security-malware-firewall')); + throw new \Exception(OSCronLocale::getInstance()->error_load_cron_env); } return $cron_file; } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronTask.php b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronTask.php index 7d71f3855..9b4821f5d 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronTask.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronTask.php @@ -50,7 +50,7 @@ private function setID() public function setStatus($status) { if (!is_string($status) || !in_array($status, $this->statuses_list)) { - throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . 'invalid arg'); + throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error_missing_property); } $this->status = $status; @@ -65,7 +65,7 @@ public function setStatus($status) public function setCommand($command) { if (!is_string($command)) { - throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . 'invalid arg'); + throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error_missing_property); } $this->command = $command; return $this; @@ -79,7 +79,7 @@ public function setCommand($command) public function setRepeatsPattern($repeats) { if (!is_string($repeats)) { - throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . 'invalid arg'); + throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error_missing_property); } $this->repeats = $repeats; return $this; @@ -93,7 +93,7 @@ public function setRepeatsPattern($repeats) public function setLineNumber($line_number) { if (!is_int($line_number)) { - throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . 'invalid arg'); + throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error_missing_property); } $this->line_number = $line_number; return $this; @@ -107,7 +107,7 @@ public function validate() { foreach (get_object_vars($this) as $property => $value) { if (is_null($value)) { - throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' - ' . $property . ' is not set'); + throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' - ' . $property . ' ' . OSCronLocale::getInstance()->error_missing_property); } } return $this; diff --git a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronView.php b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronView.php index a6565ca31..2f3d1a070 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronView.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronView.php @@ -39,7 +39,7 @@ public static function prepareTableData($table) } /** - * Convert cron time to human readable. + * Convert cron time to a human-readable string. * @param $time * @return string */ @@ -48,7 +48,7 @@ private static function timePatternToHumanReadable($time) $cronParts = explode(' ', $time); if (count($cronParts) !== 5) { - return __('Invalid cron expression', 'security-malware-firewall'); + return OSCronLocale::getInstance()->error_invalid_cron_expression; } list($minute, $hour, $dayOfMonth, $month, $dayOfWeek) = $cronParts; diff --git a/tests/Scanner/TestOSCron.php b/tests/Scanner/TestOSCron.php new file mode 100644 index 000000000..92c8e37f5 --- /dev/null +++ b/tests/Scanner/TestOSCron.php @@ -0,0 +1,181 @@ +test_cron_content = "1 * * * * echo\n\n\n5 * * * * bash\n#not a valid string"; + } + + public function testTasksSaveLoad() { + $tasks = OSCronModel::parseTasks($this->test_cron_content); + OSCronModel::saveTasksToStorage($tasks); + $saved_tasks = OSCronModel::getTasksFromStorage(false); + $this->assertEquals($tasks, $saved_tasks); + } + + public function testContentSaveLoad() { + OSCronModel::saveCronFileToStorage($this->test_cron_content); + $this->assertEquals($this->test_cron_content, OSCronModel::loadCronFileFromStorage()); + } + + public function testNoDifference() + { + OSCronModel::saveCronFileToStorage($this->test_cron_content); + $this->expectException(\Exception::class); + if (OSCronModel::loadCronFileFromStorage() === $this->test_cron_content) { + throw new \Exception(__('No difference found since last check.', 'security-malware-firewall')); + } + } + + public function testParseTasks() + { + $tasks = OSCronModel::parseTasks($this->test_cron_content); + $this->assertIsArray($tasks); + foreach ($tasks as $task) { + $this->assertInstanceOf('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task); + } + } + + public function testValidateValidTasks() + { + $task = new OSCronTask(); + $task->setCommand('echo') + ->setStatus('found') + ->setLineNumber(1) + ->setRepeatsPattern('1 * * * *') + ->validate(); + $this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task); + + $task = new OSCronTask(); + $task->setCommand('echo') + ->setStatus('approved') + ->setLineNumber(1211) + ->setRepeatsPattern('9 * * * *') + ->validate(); + $this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task); + + $task = new OSCronTask(); + $task->setCommand('echo') + ->setStatus('disabled') + ->setLineNumber(221) + ->setRepeatsPattern('10 * * * *') + ->validate(); + $this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task); + } + + public function testValidateInValidTasks() + { + $task = new OSCronTask(); + $this->expectException(\Exception::class); + $task->setCommand('echo') + ->setStatus('invalidststus') + ->setLineNumber(1) + ->setRepeatsPattern('1 * * * *') + ->validate(); + + $task = new OSCronTask(); + $this->expectException(\Exception::class); + $task->setCommand(7) + ->setStatus('approved') + ->setLineNumber(1) + ->setRepeatsPattern('1 * * * *') + ->validate(); + + $task = new OSCronTask(); + $this->expectException(\Exception::class); + $task->setCommand('echo') + ->setStatus('approved') + ->setLineNumber(array()) + ->setRepeatsPattern('1 * * * *') + ->validate(); + + $task = new OSCronTask(); + $this->expectException(\Exception::class); + $task->setCommand('echo') + ->setStatus('approved') + ->setLineNumber(2212) + ->setRepeatsPattern(array()) + ->validate(); + } + + public function testUpdateTaskFail() + { + $task = new OSCronTask(); + $task->setCommand('echo') + ->setStatus('approved') + ->setLineNumber(2212) + ->setRepeatsPattern('1 * * * *') + ->validate(); + OSCronModel::saveTasksToStorage(array($task)); + //wrong uid + $this->assertFalse(OSCronModel::updateTaskById('123', $task)); + } + + public function testUpdateTaskOK() + { + $task = new OSCronTask(); + $task->setCommand('echo') + ->setStatus('approved') + ->setLineNumber(2212) + ->setRepeatsPattern('1 * * * *') + ->validate(); + OSCronModel::saveTasksToStorage(array($task)); + $this->assertIsInt(OSCronModel::updateTaskById($task->id, $task)); + } + +// public function testApprove() +// { +// $task = new OSCronTask(); +// $task->setCommand('echo') +// ->setStatus('found') +// ->setLineNumber(2212) +// ->setRepeatsPattern('1 * * * *') +// ->validate(); +// OSCronModel::saveTasksToStorage(array($task)); +// $this->assertTrue(OSCronController::approveTask($task->id)); +// } +// +// public function testDisable() +// { +// $task = new OSCronTask(); +// $task->setCommand('echo') +// ->setStatus('found') +// ->setLineNumber(2212) +// ->setRepeatsPattern('1 * * * *') +// ->validate(); +// OSCronModel::saveTasksToStorage(array($task)); +// $this->assertTrue(OSCronController::disableTask($task->id)); +// } + + public function testisWindows() + { + $this->assertTrue(OSCronModel::isWindows()); + } + + public function testRun() + { + $result = OSCronModel::run(); + if (OSCronModel::isWindows()) { + $this->assertIsString($result); + $this->assertStringContainsString('Windows', $result); + } else { + if (true !== $result) { + $this->assertIsString($result); + } + } + } + + public function testLocale() + { + $this->assertIsString(OSCronLocale::getInstance()->error_invalid_cron_expression); + } + +}