diff options
Diffstat (limited to 'lib/Controller')
-rw-r--r-- | lib/Controller/DetectionController.php | 80 | ||||
-rw-r--r-- | lib/Controller/FileOperationController.php | 296 | ||||
-rw-r--r-- | lib/Controller/MonitoringController.php | 324 | ||||
-rw-r--r-- | lib/Controller/PageController.php (renamed from lib/Controller/RecoverController.php) | 23 | ||||
-rw-r--r-- | lib/Controller/RecoveredFileOperationController.php | 303 | ||||
-rw-r--r-- | lib/Controller/ScanController.php | 447 | ||||
-rw-r--r-- | lib/Controller/SettingsController.php (renamed from lib/Controller/BasicController.php) | 43 |
7 files changed, 697 insertions, 819 deletions
diff --git a/lib/Controller/DetectionController.php b/lib/Controller/DetectionController.php new file mode 100644 index 0000000..dc0a527 --- /dev/null +++ b/lib/Controller/DetectionController.php @@ -0,0 +1,80 @@ +<?php + +/** + * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de> + * @author Matthias Held <matthias.held@uni-konstanz.de> + * @license GNU AGPL version 3 or any later version + * + * 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/>. + */ + +namespace OCA\RansomwareDetection\Controller; + +use OCA\RansomwareDetection\AppInfo\Application; +use OCA\RansomwareDetection\Service\DetectionService; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Controller; +use OCP\IConfig; +use OCP\IUserSession; +use OCP\IRequest; + +class DetectionController extends Controller +{ + /** @var DetectionService */ + protected $detectionService; + + /** + * @param string $appName + * @param IRequest $request + * @param DetectionService $detectionService + */ + public function __construct( + $appName, + IRequest $request, + DetectionService $detectionService + ) { + parent::__construct($appName, $request); + + $this->detectionService = $detectionService; + } + + /** + * List detections. + * + * @NoCSRFRequired + * @NoAdminRequired + * + * @return JSONResponse + */ + public function findAll() { + $detections = $this->detectionService->getDetections(); + + return new JSONResponse($detections, Http::STATUS_OK); + } + + /** + * Find detection with $id. + * + * @NoCSRFRequired + * @NoAdminRequired + * + * @return JSONResponse + */ + public function find($id) { + $detection = $this->detectionService->getDetection($id); + + return new JSONResponse($detection, Http::STATUS_OK); + } +}
\ No newline at end of file diff --git a/lib/Controller/FileOperationController.php b/lib/Controller/FileOperationController.php new file mode 100644 index 0000000..1cfb281 --- /dev/null +++ b/lib/Controller/FileOperationController.php @@ -0,0 +1,296 @@ +<?php + +/** + * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de> + * @author Matthias Held <matthias.held@uni-konstanz.de> + * @license GNU AGPL version 3 or any later version + * + * 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/>. + */ + +namespace OCA\RansomwareDetection\Controller; + +use OCA\RansomwareDetection\Monitor; +use OCA\RansomwareDetection\Classifier; +use OCA\RansomwareDetection\AppInfo\Application; +use OCA\RansomwareDetection\Db\FileOperation; +use OCA\RansomwareDetection\Service\FileOperationService; +use OCA\Files_Trashbin\Trashbin; +use OCA\Files_Trashbin\Helper; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Controller; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\IConfig; +use OCP\IUserSession; +use OCP\IRequest; +use OCP\ILogger; + +class FileOperationController extends Controller +{ + /** @var IConfig */ + protected $config; + + /** @var IUserSession */ + protected $userSession; + + /** @var ILogger */ + protected $logger; + + /** @var Folder */ + protected $userFolder; + + /** @var FileOperationService */ + protected $service; + + /** @var Classifier */ + protected $classifier; + + /** @var string */ + protected $userId; + + /** + * @param string $appName + * @param IRequest $request + * @param IUserSession $userSession + * @param IConfig $config + * @param ILogger $logger + * @param Folder $userFolder + * @param FileOperationService $service + * @param Classifier $classifier + * @param string $userId + */ + public function __construct( + $appName, + IRequest $request, + IUserSession $userSession, + IConfig $config, + ILogger $logger, + Folder $userFolder, + FileOperationService $service, + Classifier $classifier, + $userId + ) { + parent::__construct($appName, $request); + + $this->config = $config; + $this->userSession = $userSession; + $this->userFolder = $userFolder; + $this->logger = $logger; + $this->service = $service; + $this->classifier = $classifier; + $this->userId = $userId; + } + + /** + * Lists the files. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @return JSONResponse + */ + public function findAll() + { + $files = $this->service->findAll(); + + foreach ($files as $file) { + $this->classifier->classifyFile($file); + } + + return new JSONResponse($files, Http::STATUS_OK); + } + + /** + * Find file with id. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @return JSONResponse + */ + public function find($id) + { + $file = $this->service->find($id); + + $this->classifier->classifyFile($file); + + return new JSONResponse($file, Http::STATUS_OK); + } + + /** + * Recover files from trashbin or remove them from normal storage. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param array $ids file operation id + * + * @return JSONResponse + */ + public function recover($ids) + { + $deleted = 0; + $recovered = 0; + $filesRecovered = array(); + $error = false; + $badRequest = false; + + foreach ($ids as $id) { + try { + $file = $this->service->find($id); + switch ($file->getCommand()) { + case Monitor::WRITE: + // Recover new created files by deleting them + $filePath = $file->getPath().'/'.$file->getOriginalName(); + if ($this->deleteFromStorage($filePath)) { + $this->service->deleteById($id, true); + + $deleted++; + array_push($filesRecovered, $id); + } else { + // File cannot be deleted + $error = true; + } + break; + case Monitor::DELETE: + // Recover deleted files by restoring them from the trashbin + // It's not necessary to use the real path + $dir = '/'; + $candidate = $this->findCandidateToRestore($dir, $file->getOriginalName()); + if ($candidate !== null) { + $path = $dir.'/'.$candidate['name'].'.d'.$candidate['mtime']; + if (Trashbin::restore($path, $candidate['name'], $candidate['mtime']) !== false) { + $this->service->deleteById($id, true); + + $recovered++; + array_push($filesRecovered, $id); + } + // File does not exist + $badRequest = false; + } else { + // No candidate found + $badRequest = false; + } + break; + case Monitor::RENAME: + $this->service->deleteById($id, true); + + $deleted++; + array_push($filesRecovered, $id); + break; + case Monitor::CREATE: + // Recover new created files/folders + $filePath = $file->getPath().'/'.$file->getOriginalName(); + if ($this->deleteFromStorage($filePath)) { + $this->service->deleteById($id, true); + + $deleted++; + array_push($filesRecovered, $id); + } else { + // File cannot be deleted + $error = true; + } + break; + default: + // All other commands need no recovery + $this->service->deleteById($id, false); + + $deleted++; + array_push($filesRecovered, $id); + break; + } + } catch (\OCP\AppFramework\Db\MultipleObjectsReturnedException $exception) { + // Found more than one with the same file name + $this->logger->debug('recover: Found more than one with the same file name.', array('app' => Application::APP_ID)); + + $badRequest = false; + } catch (\OCP\AppFramework\Db\DoesNotExistException $exception) { + // Nothing found + $this->logger->debug('recover: Files does not exist.', array('app' => Application::APP_ID)); + + $badRequest = false; + } + } + if ($error) { + return new JSONResponse(array('recovered' => $recovered, 'deleted' => $deleted, 'filesRecovered' => $filesRecovered), Http::STATUS_INTERNAL_SERVER_ERROR); + } + if ($badRequest) { + return new JSONResponse(array('recovered' => $recovered, 'deleted' => $deleted, 'filesRecovered' => $filesRecovered), Http::STATUS_BAD_REQUEST); + } + return new JSONResponse(array('recovered' => $recovered, 'deleted' => $deleted, 'filesRecovered' => $filesRecovered), Http::STATUS_OK); + } + + /** + * Deletes a file from the storage. + * + * @param string $path + * + * @return bool + */ + private function deleteFromStorage($path) + { + try { + $node = $this->userFolder->get($path); + if ($node->isDeletable()) { + $node->delete(); + } else { + return false; + } + + return true; + } catch (\OCP\Files\NotFoundException $exception) { + // Nothing found + $this->logger->debug('deleteFromStorage: Not found exception.', array('app' => Application::APP_ID)); + + return true; + } + } + + /** + * Finds a candidate to restore if a file with the specific does not exist. + * + * @param string $dir + * @param string $fileName + * + * @return FileInfo + */ + private function findCandidateToRestore($dir, $fileName) + { + $files = array(); + $trashBinFiles = $this->getTrashFiles($dir); + + foreach ($trashBinFiles as $trashBinFile) { + if (strcmp($trashBinFile['name'], $fileName) === 0) { + $files[] = $trashBinFile; + } + } + + return array_pop($files); + } + + /** + * Workaround for testing. + * + * @param string $dir + * + * @return array + */ + private function getTrashFiles($dir) + { + return Helper::getTrashFiles($dir, $this->userId, 'mtime', false); + } + +}
\ No newline at end of file diff --git a/lib/Controller/MonitoringController.php b/lib/Controller/MonitoringController.php deleted file mode 100644 index b309361..0000000 --- a/lib/Controller/MonitoringController.php +++ /dev/null @@ -1,324 +0,0 @@ -<?php - -/** - * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de> - * @author Matthias Held <matthias.held@uni-konstanz.de> - * @license GNU AGPL version 3 or any later version - * - * 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/>. - */ - -namespace OCA\RansomwareDetection\Controller; - -use OCA\RansomwareDetection\Monitor; -use OCA\RansomwareDetection\Classifier; -use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer; -use OCA\RansomwareDetection\AppInfo\Application; -use OCA\RansomwareDetection\Db\FileOperation; -use OCA\RansomwareDetection\Service\FileOperationService; -use OCA\Files_Trashbin\Trashbin; -use OCA\Files_Trashbin\Helper; -use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; -use OCP\AppFramework\OCSController; -use OCP\Files\File; -use OCP\Files\Folder; -use OCP\IConfig; -use OCP\IUserSession; -use OCP\IRequest; -use OCP\ILogger; - -class MonitoringController extends OCSController -{ - /** @var IConfig */ - protected $config; - - /** @var IUserSession */ - protected $userSession; - - /** @var Classifier */ - protected $classifier; - - /** @var ILogger */ - protected $logger; - - /** @var Folder */ - protected $userFolder; - - /** @var FileOperationService */ - protected $service; - - /** @var SequenceAnalyzer */ - protected $sequenceAnalyzer; - - /** @var string */ - protected $userId; - - /** - * @param string $appName - * @param IRequest $request - * @param IUserSession $userSession - * @param IConfig $config - * @param Classifier $classifier - * @param ILogger $logger - * @param Folder $userFolder - * @param FileOperationService $service - * @param SequenceAnalyzer $sequenceAnalyzer - * @param string $userId - */ - public function __construct( - $appName, - IRequest $request, - IUserSession $userSession, - IConfig $config, - Classifier $classifier, - ILogger $logger, - Folder $userFolder, - FileOperationService $service, - SequenceAnalyzer $sequenceAnalyzer, - $userId - ) { - parent::__construct($appName, $request); - - $this->config = $config; - $this->userSession = $userSession; - $this->classifier = $classifier; - $this->userFolder = $userFolder; - $this->logger = $logger; - $this->service = $service; - $this->sequenceAnalyzer = $sequenceAnalyzer; - $this->userId = $userId; - } - - /** - * Lists the classified files and sequences. - * - * @NoAdminRequired - * - * @return JSONResponse - */ - public function listFileOperations() - { - $files = $this->service->findAll(); - - $sequences = []; - - // Classify files and put together the sequences. - foreach ($files as $file) { - $this->classifier->classifyFile($file); - $sequences[$file->getSequence()][] = $file; - } - - $result = []; - - foreach ($sequences as $sequenceId => $sequence) { - if (sizeof($sequence) >= $this->config->getAppValue(Application::APP_ID, 'minimum_sequence_length', 0)) { - usort($sequence, function ($a, $b) { - return $b->getId() - $a->getId(); - }); - $sequenceResult = $this->sequenceAnalyzer->analyze($sequenceId, $sequence); - $sequenceInformation = ['id' => $sequenceId, 'suspicionScore' => $sequenceResult->getSuspicionScore(), 'sequence' => $sequence]; - $result[] = $sequenceInformation; - } - } - - usort($result, function ($a, $b) { - return $b['id'] - $a['id']; - }); - - return new JSONResponse($result, Http::STATUS_ACCEPTED); - } - - /** - * Exports classification and analysis data. - * - * @NoAdminRequired - * @NoCSRFRequired - * - * @param int $sequence - * - * @return JSONResponse - */ - public function export() - { - $files = $this->service->findAll(); - - $sequences = []; - - // Classify files and put together the sequences. - foreach ($files as $file) { - $this->classifier->classifyFile($file); - $sequences[$file->getSequence()][] = $file; - } - - $result = []; - - foreach ($sequences as $sequenceId => $sequence) { - if (sizeof($sequence) >= $this->config->getAppValue(Application::APP_ID, 'minimum_sequence_length', 0)) { - $result[] = $this->sequenceAnalyzer->analyze($sequenceId, $sequence); - } - } - - return new JSONResponse($result, Http::STATUS_ACCEPTED); - } - - /** - * Deletes a sequence from the database. - * - * @NoAdminRequired - * - * @param int $sequence - * - * @return JSONResponse - */ - public function deleteSequence($sequence) - { - $files = $this->service->deleteSequenceById($sequence); - - return new JSONResponse(['status' => 'success'], Http::STATUS_ACCEPTED); - } - - /** - * Recover files from trashbin or remove them from normal storage. - * - * @NoAdminRequired - * - * @param int $id file operation id - * - * @return JSONResponse - */ - public function recover($id) - { - try { - $file = $this->service->find($id); - if ($file->getCommand() === Monitor::WRITE) { - // Recover new created files by deleting them - $filePath = $file->getPath().'/'.$file->getOriginalName(); - if ($this->deleteFromStorage($filePath)) { - $this->service->deleteById($id); - - return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK); - } else { - return new JSONResponse(['status' => 'error', 'message' => 'File cannot be deleted.'], Http::STATUS_BAD_REQUEST); - } - } elseif ($file->getCommand() === Monitor::DELETE) { - // Recover deleted files by restoring them from the trashbin - // It's not necessary to use the real path - $dir = '/'; - $candidate = $this->findCandidateToRestore($dir, $file->getOriginalName()); - if ($candidate !== null) { - $path = $dir.'/'.$candidate['name'].'.d'.$candidate['mtime']; - if (Trashbin::restore($path, $candidate['name'], $candidate['mtime']) !== false) { - $this->service->deleteById($id); - - return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK); - } - - return new JSONResponse(['status' => 'error', 'message' => 'File does not exist.', 'path' => $path, 'name' => $candidate['name'], 'mtime' => $candidate['mtime']], Http::STATUS_BAD_REQUEST); - } else { - return new JSONResponse(['status' => 'error', 'message' => 'No candidate found.'], Http::STATUS_BAD_REQUEST); - } - } elseif ($file->getCommand() === Monitor::RENAME) { - $this->service->deleteById($id); - - return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK); - } elseif ($file->getCommand() === Monitor::CREATE) { - // Recover new created folders - $filePath = $file->getPath().'/'.$file->getOriginalName(); - if ($this->deleteFromStorage($filePath)) { - $this->service->deleteById($id); - - return new JSONResponse(['status' => 'success', 'id' => $id], Http::STATUS_OK); - } else { - return new JSONResponse(['status' => 'error', 'message' => 'File cannot be deleted.'], Http::STATUS_BAD_REQUEST); - } - } else { - // All other commands need no recovery - $this->service->deleteById($id); - - return new JSONResponse(['id' => $id], Http::STATUS_OK); - } - } catch (\OCP\AppFramework\Db\MultipleObjectsReturnedException $exception) { - // Found more than one with the same file name - $this->logger->debug('recover: Found more than one with the same file name.', array('app' => Application::APP_ID)); - - return new JSONResponse(['status' => 'error', 'message' => 'Found more than one with the same file name.'], Http::STATUS_BAD_REQUEST); - } catch (\OCP\AppFramework\Db\DoesNotExistException $exception) { - // Nothing found - $this->logger->debug('recover: Files does not exist.', array('app' => Application::APP_ID)); - - return new JSONResponse(['status' => 'error', 'message' => 'Files does not exist.'], Http::STATUS_BAD_REQUEST); - } - } - - /** - * Deletes a file from the storage. - * - * @param string $path - * - * @return bool - */ - private function deleteFromStorage($path) - { - try { - $node = $this->userFolder->get($path); - if ($node->isDeletable()) { - $node->delete(); - } else { - return false; - } - - return true; - } catch (\OCP\Files\NotFoundException $exception) { - // Nothing found - $this->logger->debug('deleteFromStorage: Not found exception.', array('app' => Application::APP_ID)); - - return true; - } - } - - /** - * Finds a candidate to restore if a file with the specific does not exist. - * - * @param string $dir - * @param string $fileName - * - * @return FileInfo - */ - private function findCandidateToRestore($dir, $fileName) - { - $files = array(); - $trashBinFiles = $this->getTrashFiles($dir); - - foreach ($trashBinFiles as $trashBinFile) { - if (strcmp($trashBinFile['name'], $fileName) === 0) { - $files[] = $trashBinFile; - } - } - - return array_pop($files); - } - - /** - * Workaround for testing. - * - * @param string $dir - * - * @return array - */ - private function getTrashFiles($dir) - { - return Helper::getTrashFiles($dir, $this->userId, 'mtime', false); - } -} diff --git a/lib/Controller/RecoverController.php b/lib/Controller/PageController.php index 52497df..6827f5d 100644 --- a/lib/Controller/RecoverController.php +++ b/lib/Controller/PageController.php @@ -22,27 +22,23 @@ namespace OCA\RansomwareDetection\Controller; use OCA\RansomwareDetection\AppInfo\Application; +use OCA\RansomwareDetection\Service\FileOperationService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; use OCP\IRequest; -class RecoverController extends Controller +class PageController extends Controller { - /** @var int */ - private $userId; /** * @param string $appName * @param IRequest $request - * @param string $userId */ public function __construct( $appName, - IRequest $request, - $userId + IRequest $request ) { parent::__construct($appName, $request); - $this->userId = $userId; } /** @@ -57,17 +53,4 @@ class RecoverController extends Controller { return new TemplateResponse(Application::APP_ID, 'index'); } - - /** - * Scan page. - * - * @NoAdminRequired - * @NoCSRFRequired - * - * @return TemplateResponse - */ - public function scan() - { - return new TemplateResponse(Application::APP_ID, 'scan'); - } } diff --git a/lib/Controller/RecoveredFileOperationController.php b/lib/Controller/RecoveredFileOperationController.php new file mode 100644 index 0000000..e17d3c3 --- /dev/null +++ b/lib/Controller/RecoveredFileOperationController.php @@ -0,0 +1,303 @@ +<?php + +/** + * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de> + * @author Matthias Held <matthias.held@uni-konstanz.de> + * @license GNU AGPL version 3 or any later version + * + * 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/>. + */ + +namespace OCA\RansomwareDetection\Controller; + +use OCA\RansomwareDetection\Monitor; +use OCA\RansomwareDetection\Classifier; +use OCA\RansomwareDetection\AppInfo\Application; +use OCA\RansomwareDetection\Db\FileOperation; +use OCA\RansomwareDetection\Service\RecoveredFileOperationService; +use OCA\Files_Trashbin\Trashbin; +use OCA\Files_Trashbin\Helper; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Controller; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\IConfig; +use OCP\IUserSession; +use OCP\IRequest; +use OCP\ILogger; + +class RecoveredFileOperationController extends Controller +{ + /** @var IConfig */ + protected $config; + + /** @var IUserSession */ + protected $userSession; + + /** @var ILogger */ + protected $logger; + + /** @var Folder */ + protected $userFolder; + + /** @var RecoveredFileOperationService */ + protected $service; + + /** @var Classifier */ + protected $classifier; + + /** @var string */ + protected $userId; + + /** + * @param string $appName + * @param IRequest $request + * @param IUserSession $userSession + * @param IConfig $config + * @param ILogger $logger + * @param Folder $userFolder + * @param RecoveredFileOperationService $service + * @param Classifier $classifier + * @param string $userId + */ + public function __construct( + $appName, + IRequest $request, + IUserSession $userSession, + IConfig $config, + ILogger $logger, + Folder $userFolder, + RecoveredFileOperationService $service, + Classifier $classifier, + $userId + ) { + parent::__construct($appName, $request); + + $this->config = $config; + $this->userSession = $userSession; + $this->userFolder = $userFolder; + $this->logger = $logger; + $this->service = $service; + $this->classifier = $classifier; + $this->userId = $userId; + } + + /** + * Lists the files. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @return JSONResponse + */ + public function findAll() + { + $files = $this->service->findAll(); + + foreach ($files as $file) { + $this->classifier->classifyFile($file); + } + + return new JSONResponse($files, Http::STATUS_OK); + } + + /** + * Find file with id. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @return JSONResponse + */ + public function find($id) + { + $file = $this->service->find($id); + + $this->classifier->classifyFile($file); + + return new JSONResponse($file, Http::STATUS_OK); + } + + /** + * Recover files from trashbin or remove them from normal storage. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param array $ids file operation id + * + * @return JSONResponse + */ + public function recover($ids) + { + $deleted = 0; + $recovered = 0; + $filesRecovered = array(); + $error = false; + $badRequest = false; + + foreach ($ids as $id) { + try { + $file = $this->service->find($id); + switch ($file->getCommand()) { + case Monitor::DELETE: + // Recover new created files by deleting them + $filePath = $file->getPath().'/'.$file->getOriginalName(); + if ($this->deleteFromStorage($filePath)) { + $this->service->deleteById($id); + + $deleted++; + array_push($filesRecovered, $id); + } else { + // File cannot be deleted + $error = true; + } + break; + case Monitor::WRITE: + // Recover deleted files by restoring them from the trashbin + // It's not necessary to use the real path + $dir = '/'; + $candidate = $this->findCandidateToRestore($dir, $file->getOriginalName()); + if ($candidate !== null) { + $path = $dir.'/'.$candidate['name'].'.d'.$candidate['mtime']; + if (Trashbin::restore($path, $candidate['name'], $candidate['mtime']) !== false) { + $this->service->deleteById($id); + + $recovered++; + array_push($filesRecovered, $id); + } + // File does not exist + $badRequest = false; + } else { + // No candidate found + $badRequest = false; + } + break; + case Monitor::RENAME: + $this->service->deleteById($id); + + $deleted++; + array_push($filesRecovered, $id); + break; + case Monitor::CREATE: + // Recover deleted files by restoring them from the trashbin + // It's not necessary to use the real path + $dir = '/'; + $candidate = $this->findCandidateToRestore($dir, $file->getOriginalName()); + if ($candidate !== null) { + $path = $dir.'/'.$candidate['name'].'.d'.$candidate['mtime']; + if (Trashbin::restore($path, $candidate['name'], $candidate['mtime']) !== false) { + $this->service->deleteById($id); + + $recovered++; + array_push($filesRecovered, $id); + } + // File does not exist + $badRequest = false; + } else { + // No candidate found + $badRequest = false; + } + break; + default: + // All other commands need no recovery + $this->service->deleteById($id); + + $deleted++; + array_push($filesRecovered, $id); + break; + } + } catch (\OCP\AppFramework\Db\MultipleObjectsReturnedException $exception) { + // Found more than one with the same file name + $this->logger->debug('recover: Found more than one with the same file name.', array('app' => Application::APP_ID)); + + $badRequest = false; + } catch (\OCP\AppFramework\Db\DoesNotExistException $exception) { + // Nothing found + $this->logger->debug('recover: Files does not exist.', array('app' => Application::APP_ID)); + + $badRequest = false; + } + } + if ($error) { + return new JSONResponse(array('recovered' => $recovered, 'deleted' => $deleted, 'filesRecovered' => $filesRecovered), Http::STATUS_INTERNAL_SERVER_ERROR); + } + if ($badRequest) { + return new JSONResponse(array('recovered' => $recovered, 'deleted' => $deleted, 'filesRecovered' => $filesRecovered), Http::STATUS_BAD_REQUEST); + } + return new JSONResponse(array('recovered' => $recovered, 'deleted' => $deleted, 'filesRecovered' => $filesRecovered), Http::STATUS_OK); + } + + /** + * Deletes a file from the storage. + * + * @param string $path + * + * @return bool + */ + private function deleteFromStorage($path) + { + try { + $node = $this->userFolder->get($path); + if ($node->isDeletable()) { + $node->delete(); + } else { + return false; + } + + return true; + } catch (\OCP\Files\NotFoundException $exception) { + // Nothing found + $this->logger->debug('deleteFromStorage: Not found exception.', array('app' => Application::APP_ID)); + + return true; + } + } + + /** + * Finds a candidate to restore if a file with the specific does not exist. + * + * @param string $dir + * @param string $fileName + * + * @return FileInfo + */ + private function findCandidateToRestore($dir, $fileName) + { + $files = array(); + $trashBinFiles = $this->getTrashFiles($dir); + + foreach ($trashBinFiles as $trashBinFile) { + if (strcmp($trashBinFile['name'], $fileName) === 0) { + $files[] = $trashBinFile; + } + } + + return array_pop($files); + } + + /** + * Workaround for testing. + * + * @param string $dir + * + * @return array + */ + private function getTrashFiles($dir) + { + return Helper::getTrashFiles($dir, $this->userId, 'mtime', false); + } + +}
\ No newline at end of file diff --git a/lib/Controller/ScanController.php b/lib/Controller/ScanController.php deleted file mode 100644 index a7eca58..0000000 --- a/lib/Controller/ScanController.php +++ /dev/null @@ -1,447 +0,0 @@ -<?php - -/** - * @copyright Copyright (c) 2018 Matthias Held <matthias.held@uni-konstanz.de> - * @author Matthias Held <matthias.held@uni-konstanz.de> - * @license GNU AGPL version 3 or any later version - * - * 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/>. - */ - -namespace OCA\RansomwareDetection\Controller; - -use OCA\RansomwareDetection\Monitor; -use OCA\RansomwareDetection\Classifier; -use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer; -use OCA\RansomwareDetection\Analyzer\EntropyAnalyzer; -use OCA\RansomwareDetection\Analyzer\FileCorruptionAnalyzer; -use OCA\RansomwareDetection\Analyzer\FileExtensionAnalyzer; -use OCA\RansomwareDetection\Analyzer\FileExtensionResult; -use OCA\RansomwareDetection\AppInfo\Application; -use OCA\RansomwareDetection\Db\FileOperation; -use OCA\RansomwareDetection\Exception\NotAFileException; -use OCA\RansomwareDetection\Service\FileOperationService; -use OCA\RansomwareDetection\Scanner\StorageStructure; -use OCP\Files\NotFoundException; -use OCA\Files_Trashbin\Trashbin; -use OCA\Files_Trashbin\Helper; -use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; -use OCP\AppFramework\OCSController; -use OCP\Files\File; -use OCP\Files\Folder; -use OCP\IConfig; -use OCP\IUserSession; -use OCP\IRequest; -use OCP\IDBConnection; -use OCP\ILogger; - -class ScanController extends OCSController -{ - /** @var IConfig */ - protected $config; - - /** @var IUserSession */ - protected $userSession; - - /** @var Classifier */ - protected $classifier; - - /** @var ILogger */ - protected $logger; - - /** @var Folder */ - protected $userFolder; - - /** @var FileOperationService */ - protected $service; - - /** @var SequenceAnalyzer */ - protected $sequenceAnalyzer; - - /** @var EntropyAnalyzer */ - protected $entropyAnalyzer; - - /** @var FileCorruptionAnalyzer */ - protected $fileCorruptionAnalyzer; - - /** @var FileExtensionAnalyzer */ - protected $fileExtensionAnalyzer; - - /** @var IDBConnection */ - protected $connection; - - /** @var string */ - protected $userId; - - /** - * @param string $appName - * @param IRequest $request - * @param IUserSession $userSession - * @param IConfig $config - * @param Classifier $classifier - * @param ILogger $logger - * @param Folder $userFolder - * @param FileOperationService $service - * @param SequenceAnalyzer $sequenceAnalyzer - * @param EntropyAnalyzer $entropyAnalyzer - * @param FileCorruptionAnalyzer $fileCorruptionAnalyzer - * @param FileExtensionAnalyzer $fileExtensionAnalyzer - * @param IDBConnection $connection - * @param string $userId - */ - public function __construct( - $appName, - IRequest $request, - IUserSession $userSession, - IConfig $config, - Classifier $classifier, - ILogger $logger, - Folder $userFolder, - FileOperationService $service, - SequenceAnalyzer $sequenceAnalyzer, - EntropyAnalyzer $entropyAnalyzer, - FileCorruptionAnalyzer $fileCorruptionAnalyzer, - FileExtensionAnalyzer $fileExtensionAnalyzer, - IDBConnection $connection, - $userId - ) { - parent::__construct($appName, $request); - - $this->config = $config; - $this->userSession = $userSession; - $this->classifier = $classifier; - $this->userFolder = $userFolder; - $this->logger = $logger; - $this->service = $service; - $this->sequenceAnalyzer = $sequenceAnalyzer; - $this->entropyAnalyzer = $entropyAnalyzer; - $this->fileCorruptionAnalyzer = $fileCorruptionAnalyzer; - $this->fileExtensionAnalyzer = $fileExtensionAnalyzer; - $this->connection = $connection; - $this->userId = $userId; - } - - /** - * Post scan recovery. - * - * @NoAdminRequired - * - * @param integer $id - * @param integer $sequence - * @param integer $command - * @param string $path - * @param string $name - * @param integer $timestamp - * - * @return JSONResponse - */ - public function recover($id, $sequence, $command, $path, $name, $timestamp) - { - if ($command === Monitor::WRITE) { - // Delete file - if ($this->deleteFromStorage($path . '/' . $name)) { - return new JSONResponse(['status' => 'success', 'id' => $id, 'sequence' => $sequence], Http::STATUS_OK); - } else { - return new JSONResponse(['status' => 'error', 'message' => 'File cannot be deleted.'], Http::STATUS_OK); - } - } else if ($command === Monitor::DELETE) { - // Restore file - $trashPath = '/'.$name.'.d'.$timestamp;; - if ($this->restoreFromTrashbin($trashPath, $name, $timestamp) !== false) { - return new JSONResponse(['status' => 'success', 'id' => $id, 'sequence' => $sequence], Http::STATUS_OK); - } - - return new JSONResponse(['status' => 'error', 'message' => 'File does not exist.', 'path' => $trashPath, 'name' => $name, 'mtime' => $timestamp], Http::STATUS_OK); - } else { - // Scan can only detect WRITE and DELETE this should never happen. - $this->logger->error('postRecover: RENAME or CREATE operation.', array('app' => Application::APP_ID)); - return new JSONResponse(['status' => 'error', 'message' => 'Wrong command.'], Http::STATUS_BAD_REQUEST); - } - - } - - /** - * The files to scan. - * - * @NoAdminRequired - * - * @return JSONResponse - */ - public function filesToScan() - { - $start = time(); - $storageStructure = $this->getStorageStructure($this->userFolder); - $trashStorageStructure = $this->getTrashStorageStructure(); - - $allFiles = array(); - - // convert file to json and merge into one array - $files = $storageStructure->getFiles(); - for ($i = 0; $i < count($files); $i++) { - $allFiles[] = ['id' => $files[$i]->getId(), 'path' => $files[$i]->getInternalPath(), 'timestamp' => $this->getLastActivity($files[$i]->getId())['timestamp']]; - } - $trashFiles = $trashStorageStructure->getFiles(); - for ($i = 0; $i < count($trashFiles); $i++) { - $allFiles[] = ['id' => $trashFiles[$i]->getId(), 'path' => $trashFiles[$i]->getInternalPath(), 'timestamp' => $trashFiles[$i]->getMtime()]; - } - - // sort ASC for timestamp - usort($allFiles, function ($a, $b) { - if ($a['timestamp'] === $b['timestamp']) { - return 0; - } - return $a['timestamp'] - $b['timestamp']; - }); - - // build sequences - $sequencesArray = array(); - $sequence = array(); - for ($i = 0; $i < count($allFiles); $i++) { - if ($i === 0) { - $sequence = array(); - } else { - if ($allFiles[$i]['timestamp'] - $allFiles[$i - 1]['timestamp'] > 180) { - $sequencesArray[] = $sequence; - $sequence = array(); - } - } - $sequence[] = $allFiles[$i]; - } - $sequencesArray[] = $sequence; - $end = time(); - - return new JSONResponse(['status' => 'success', 'sequences' => $sequencesArray, 'numberOfFiles' => $storageStructure->getNumberOfFiles(), 'scanDuration' => $end - $start], Http::STATUS_OK); - } - - /** - * Scan sequence. - * - * @NoAdminRequired - * - * @param string $sequence - * @return JSONResponse - */ - public function scanSequence($sequence) { - if (sizeof($sequence) > $this->config->getAppValue(Application::APP_ID, 'minimum_sequence_length', 0)) { - $sequenceResults = array(); - foreach ($sequence as $file) { - try { - $fileOperation = $this->buildFileOperation($file); - } catch (NotAFileException $exception) { - $this->logger->debug('scanSequence: Path to file doesn\'t lead to file object.', array('app' => Application::APP_ID)); - continue; - } catch (NotFoundException $exception) { - $this->logger->debug('scanSequence: Not found.', array('app' => Application::APP_ID)); - continue; - } - - $this->classifier->classifyFile($fileOperation); - $jsonSequence[] = ['userId' => $fileOperation->getUserId(), 'path' => $fileOperation->getPath(), 'originalName' => preg_replace('/.d[0-9]{10}/', '', $fileOperation->getOriginalName()), - 'type' => $fileOperation->getType(), 'mimeType' => $fileOperation->getMimeType(), 'size' => $fileOperation->getSize(), 'corrupted' => $fileOperation->getCorrupted(), 'timestamp' => $fileOperation->getTimestamp(), 'entropy' => $fileOperation->getEntropy(), - 'standardDeviation' => $fileOperation->getStandardDeviation(), 'command' => $fileOperation->getCommand(), 'fileClass' => $fileOperation->getFileClass(), 'fileExtensionClass' => $fileOperation->getFileExtensionClass(), 'suspicionClass' => $fileOperation->getSuspicionClass()]; - $fileOperationSequence[] = $fileOperation; - } - if (sizeof($fileOperationSequence) > 0) { - $sequenceResult = $this->sequenceAnalyzer->analyze(0, $fileOperationSequence); - return new JSONResponse(['status' => 'success', 'suspicionScore' => $sequenceResult->getSuspicionScore(), 'sequence' => $jsonSequence], Http::STATUS_OK); - } else { - return new JSONResponse(['status' => 'error', 'message' => 'The file(s) requested do(es) not exist.']); - } - } else { - return new JSONResponse(['status' => 'error', 'message' => 'Sequence is to short.'], Http::STATUS_OK); - } - } - - /** - * Just for testing purpose to mock the external static method. - * - * @return array - */ - protected function getTrashFiles() { - return Helper::getTrashFiles("/", $this->userId, 'mtime', false); - } - - /** - * Just for testing purpose to mock the external static method. - * - * @param string $trashPath - * @param array $pathInfo - * @param integer $timestamp - * @return boolean - */ - protected function restoreFromTrashbin($trashPath, $name, $timestamp) - { - return Trashbin::restore($trashPath, $name, $timestamp); - } - - /** - * Get last activity. - * Visibility 'protected' is that it's possible to mock the database access. - * - * @param $objectId - */ - protected function getLastActivity($objectId) - { - $query = $this->connection->getQueryBuilder(); - $query->select('*')->from('activity'); - $query->where($query->expr()->eq('affecteduser', $query->createNamedParameter($this->userId))) - ->andWhere($query->expr()->eq('object_id', $query->createNamedParameter($objectId))); - $result = $query->execute(); - while ($row = $result->fetch()) { - $rows[] = $row; - } - $result->closeCursor(); - if (isset($rows) && is_array($rows)) { - return array_pop($rows); - } else { - $this->logger->debug('getLastActivity: No activity found.', array('app' => Application::APP_ID)); - return 0; - } - } - - /** - * Builds a file operations from a file info array. - * - * @param array $file - * @return FileOperation - */ - private function buildFileOperation($file) - { - $fileOperation = new FileOperation(); - $fileOperation->setUserId($this->userId); - if (strpos($file['path'], 'files_trashbin') !== false) { - $node = $this->userFolder->getParent()->get($file['path'] . '.d' . $file['timestamp']); - $fileOperation->setCommand(Monitor::DELETE); - $fileOperation->setTimestamp($file['timestamp']); - $pathInfo = pathinfo($node->getInternalPath()); - $fileOperation->setPath($pathInfo['dirname']); - } else { - $node = $this->userFolder->getParent()->get($file['path']); - $lastActivity = $this->getLastActivity($file['id']); - $fileOperation->setCommand(Monitor::WRITE); - $fileOperation->setTimestamp($lastActivity['timestamp']); - $pathInfo = pathinfo($node->getInternalPath()); - $fileOperation->setPath(str_replace('files', '', $pathInfo['dirname'])); - } - if (!($node instanceof File)) { - throw new NotAFileException(); - } - $fileOperation->setOriginalName($node->getName()); - $fileOperation->setType('file'); - $fileOperation->setMimeType($node->getMimeType()); - $fileOperation->setSize($node->getSize()); - $fileOperation->setTimestamp($file['timestamp']); - - // file extension analysis - $fileExtensionResult = $this->fileExtensionAnalyzer->analyze($node->getInternalPath()); - $fileOperation->setFileExtensionClass($fileExtensionResult->getFileExtensionClass()); - - $fileCorruptionResult = $this->fileCorruptionAnalyzer->analyze($node); - $isCorrupted = $fileCorruptionResult->isCorrupted(); - $fileOperation->setCorrupted($isCorrupted); - if ($isCorrupted) { - $fileOperation->setFileExtensionClass(FileExtensionResult::SUSPICIOUS); - } - - // entropy analysis - $entropyResult = $this->entropyAnalyzer->analyze($node); - $fileOperation->setEntropy($entropyResult->getEntropy()); - $fileOperation->setStandardDeviation($entropyResult->getStandardDeviation()); - $fileOperation->setFileClass($entropyResult->getFileClass()); - - return $fileOperation; - } - - /** - * Get trash storage structure. - * - * @return StorageStructure - */ - private function getTrashStorageStructure() - { - $storageStructure = new StorageStructure(0, []); - $nodes = $this->getTrashFiles(); - foreach ($nodes as $node) { - $storageStructure->addFile($node); - $storageStructure->increaseNumberOfFiles(); - } - return $storageStructure; - } - - /** - * Get storage structure recursively. - * - * @param INode $node - * - * @return StorageStructure - */ - private function getStorageStructure($node) - { - // set count for node to 0 - $storageStructure = new StorageStructure(0, []); - if ($node instanceof Folder) { - // it's a folder - $nodes = $node->getDirectoryListing(); - if (count($nodes) === 0) { - // folder is empty so nothing to do - return $storageStructure; - } - foreach ($nodes as $tmpNode) { - // analyse files in subfolder - $tmpStorageStructure = $this->getStorageStructure($tmpNode); - $storageStructure->setFiles(array_merge($storageStructure->getFiles(), $tmpStorageStructure->getFiles())); - $storageStructure->setNumberOfFiles($storageStructure->getNumberOfFiles() + $tmpStorageStructure->getNumberOfFiles()); - } - return $storageStructure; - } - else if ($node instanceof File) { - // it's a file - $storageStructure->addFile($node); - $storageStructure->increaseNumberOfFiles(); - return $storageStructure; - } - else { - // it's me Mario. - // there is nothing else than file or folder - $this->logger->error('getStorageStructure: Neither file nor folder.', array('app' => Application::APP_ID)); - } - } - - /** - * Deletes a file from the storage. - * - * @param string $path - * - * @return bool - */ - private function deleteFromStorage($path) - { - try { - $node = $this->userFolder->get($path); - if ($node instanceof File && $node->isDeletable()) { - $node->delete(); - } else { - return false; - } - - return true; - } catch (\OCP\Files\NotFoundException $exception) { - // Nothing found - $this->logger->debug('deleteFromStorage: Not found exception.', array('app' => Application::APP_ID)); - - return true; - } - } -} diff --git a/lib/Controller/BasicController.php b/lib/Controller/SettingsController.php index 2d72f50..dd00909 100644 --- a/lib/Controller/BasicController.php +++ b/lib/Controller/SettingsController.php @@ -22,14 +22,15 @@ namespace OCA\RansomwareDetection\Controller; use OCA\RansomwareDetection\AppInfo\Application; +use OCA\RansomwareDetection\Model\Settings; use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; -use OCP\AppFramework\OCSController; +use OCP\AppFramework\Controller; use OCP\IConfig; use OCP\IUserSession; use OCP\IRequest; -class BasicController extends OCSController +class SettingsController extends Controller { /** @var IConfig */ protected $config; @@ -62,46 +63,32 @@ class BasicController extends OCSController } /** - * Get debug mode. + * Get settings. * + * @NoCSRFRequired * @NoAdminRequired * * @return JSONResponse */ - public function getDebugMode() - { - $debugMode = $this->config->getAppValue(Application::APP_ID, 'debug', 0); + public function findAll() { + $debug = $this->config->getAppValue(Application::APP_ID, 'debug', 0); + $color = $this->config->getUserValue($this->userId, Application::APP_ID, 'color_mode', 0); + $settings = new Settings($debug, $color); - return new JSONResponse(['status' => 'success', 'message' => 'Get debug mode.', 'debugMode' => $debugMode], Http::STATUS_ACCEPTED); + return new JSONResponse($settings, Http::STATUS_OK); } /** - * Get color mode. + * Set settings. * + * @NoCSRFRequired * @NoAdminRequired * * @return JSONResponse */ - public function getColorMode() - { - $colorMode = $this->config->getUserValue($this->userId, Application::APP_ID, 'color_mode', 0); + public function update($color, $debug) { + $this->config->setUserValue($this->userId, Application::APP_ID, 'color_mode', $color); - return new JSONResponse(['status' => 'success', 'message' => 'Get color mode.', 'colorMode' => $colorMode], Http::STATUS_ACCEPTED); - } - - /** - * Changes color mode. - * - * @NoAdminRequired - * - * @param int $colorMode - * - * @return JSONResponse - */ - public function changeColorMode($colorMode) - { - $this->config->setUserValue($this->userId, Application::APP_ID, 'color_mode', $colorMode); - - return new JSONResponse(['status' => 'success', 'message' => 'Color mode changed.'], Http::STATUS_ACCEPTED); + return new JSONResponse(null, Http::STATUS_OK); } } |