diff options
author | Matthias <ilovemilk@wusa.io> | 2020-09-14 19:15:26 +0300 |
---|---|---|
committer | Matthias <ilovemilk@wusa.io> | 2020-09-14 19:15:26 +0300 |
commit | dfeda8d4bdd88d5ec99a87610b47ade98a8065d7 (patch) | |
tree | 538b60cacac6e2e53f838e3d1d61702cbe47dd42 | |
parent | 8981ac7f42307d3b56e76a3aae9a99d4c6f83200 (diff) |
add recovered file operations
-rw-r--r-- | appinfo/database.xml | 99 | ||||
-rw-r--r-- | appinfo/info.xml | 2 | ||||
-rw-r--r-- | appinfo/routes.php | 4 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 16 | ||||
-rw-r--r-- | lib/Controller/FileOperationController.php | 10 | ||||
-rw-r--r-- | lib/Controller/RecoveredFileOperationController.php | 296 | ||||
-rw-r--r-- | lib/Db/FileOperation.php | 21 | ||||
-rw-r--r-- | lib/Db/RecoveredFileOperation.php | 110 | ||||
-rw-r--r-- | lib/Db/RecoveredFileOperationMapper.php | 156 | ||||
-rw-r--r-- | lib/Service/FileOperationService.php | 22 | ||||
-rw-r--r-- | lib/Service/RecoveredFileOperationService.php | 159 | ||||
-rw-r--r-- | src/views/History.vue | 4 |
12 files changed, 890 insertions, 9 deletions
diff --git a/appinfo/database.xml b/appinfo/database.xml index 261e631..12d4170 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -103,4 +103,103 @@ </field> </declaration> </table> + <table> + <name>*dbprefix*ransomware_detection_recovered</name> + <declaration> + <field> + <name>id</name> + <type>integer</type> + <notnull>true</notnull> + <autoincrement>true</autoincrement> + </field> + <field> + <name>user_id</name> + <type>text</type> + <notnull>true</notnull> + <length>255</length> + </field> + <field> + <name>path</name> + <type>text</type> + <notnull>false</notnull> + <length>255</length> + </field> + <field> + <name>original_name</name> + <type>text</type> + <notnull>false</notnull> + <length>255</length> + </field> + <field> + <name>new_name</name> + <type>text</type> + <notnull>false</notnull> + <length>255</length> + </field> + <field> + <name>type</name> + <type>text</type> + <notnull>false</notnull> + <length>255</length> + </field> + <field> + <name>mime_type</name> + <type>text</type> + <notnull>false</notnull> + <length>255</length> + </field> + <field> + <name>size</name> + <type>integer</type> + <notnull>false</notnull> + <length>32</length> + </field> + <field> + <name>corrupted</name> + <type>integer</type> + <notnull>false</notnull> + <length>32</length> + </field> + <field> + <name>timestamp</name> + <type>integer</type> + <notnull>true</notnull> + <length>12</length> + </field> + <field> + <name>command</name> + <type>integer</type> + <notnull>true</notnull> + <length>12</length> + </field> + <field> + <name>sequence</name> + <type>integer</type> + <notnull>true</notnull> + <length>255</length> + </field> + <field> + <name>entropy</name> + <type>float</type> + <notnull>false</notnull> + </field> + <field> + <name>standard_deviation</name> + <type>float</type> + <notnull>false</notnull> + </field> + <field> + <name>file_class</name> + <type>text</type> + <notnull>false</notnull> + <length>64</length> + </field> + <field> + <name>file_extension_class</name> + <type>text</type> + <notnull>false</notnull> + <length>64</length> + </field> + </declaration> + </table> </database> diff --git a/appinfo/info.xml b/appinfo/info.xml index 5095179..2e5762d 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ <name>Ransomware recovery</name> <summary><![CDATA[This app offers synchronization monitoring and a file storage scanner for a guided user-controlled one-step ransomare recovery.]]></summary> <description><![CDATA[This app monitors file operations during the synchronization to detect ransomware attacks and also offers a post infection file storage scanner, which works even if it happend that you didn't have this app installed during an attack. This is done by using generic indicators for a guided user-controlled one-step recovery utilizing the integrated file versioning methods. Sponsored by the German Federal Ministry of Education and Research, and Prototype Fund.]]></description> - <version>0.8.0</version> + <version>0.8.2</version> <licence>agpl</licence> <author mail="matthias.held@uni-konstanz.de">Matthias Held</author> <namespace>RansomwareDetection</namespace> diff --git a/appinfo/routes.php b/appinfo/routes.php index d2b1631..1cb9768 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -15,6 +15,10 @@ return [ ['name' => 'fileOperation#findAll', 'url' => '/api/{apiVersion}/file-operation', 'verb' => 'GET', 'requirements' => ['apiVersion' => 'v1']], ['name' => 'fileOperation#find', 'url' => '/api/{apiVersion}/file-operation/{id}', 'verb' => 'GET', 'requirements' => ['apiVersion' => 'v1']], ['name' => 'fileOperation#recover', 'url' => '/api/{apiVersion}/file-operations/recover', 'verb' => 'PUT', 'requirements' => ['apiVersion' => 'v1']], + // Recovered file operation controller + ['name' => 'recoveredFileOperation#findAll', 'url' => '/api/{apiVersion}/recovered/file-operation', 'verb' => 'GET', 'requirements' => ['apiVersion' => 'v1']], + ['name' => 'recoveredFileOperation#find', 'url' => '/api/{apiVersion}/recovered/file-operation/{id}', 'verb' => 'GET', 'requirements' => ['apiVersion' => 'v1']], + ['name' => 'recoveredFileOperation#recover', 'url' => '/api/{apiVersion}/recovered/file-operations/recover', 'verb' => 'PUT', 'requirements' => ['apiVersion' => 'v1']], // Settings controller ['name' => 'settings#update', 'url' => '/api/{apiVersion}/settings', 'verb' => 'PUT', 'requirements' => ['apiVersion' => 'v1']], ['name' => 'settings#findAll', 'url' => '/api/{apiVersion}/settings', 'verb' => 'GET', 'requirements' => ['apiVersion' => 'v1']], diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 5bdad38..82f724a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -39,8 +39,10 @@ use OCA\RansomwareDetection\Notification\Notifier; use OCA\RansomwareDetection\StorageWrapper; use OCA\RansomwareDetection\Connector\Sabre\RequestPlugin; use OCA\RansomwareDetection\Service\FileOperationService; +use OCA\RansomwareDetection\Service\RecoveredFileOperationService; use OCA\RansomwareDetection\Service\DetectionService; use OCA\RansomwareDetection\Mapper\FileOperationMapper; +use OCA\RansomwareDetection\Mapper\RecoveredFileOperationMapper; use OCP\AppFramework\App; use OCP\App\IAppManager; use OCP\Files\IRootFolder; @@ -72,6 +74,12 @@ class Application extends App ); }); + $container->registerService('RecoveredFileOperationMapper', function ($c) { + return new RecoveredFileOperationMapper( + $c->query('ServerContainer')->getDb() + ); + }); + $container->registerService('DetectionDeserializer', function ($c) { return new DetectionDeserializer( $c->query('FileOperationMapper') @@ -86,6 +94,14 @@ class Application extends App ); }); + $container->registerService('RecoveredFileOperationService', function ($c) { + return new RecoveredFileOperationService( + $c->query(FileOperationMapper::class), + $c->query(RecoveredFileOperationMapper::class), + $c->query('ServerContainer')->getUserSession()->getUser()->getUID() + ); + }); + $container->registerService('DetectionService', function ($c) { return new DetectionService( $c->query(ILogger::class), diff --git a/lib/Controller/FileOperationController.php b/lib/Controller/FileOperationController.php index d09268e..1cfb281 100644 --- a/lib/Controller/FileOperationController.php +++ b/lib/Controller/FileOperationController.php @@ -156,7 +156,7 @@ class FileOperationController extends Controller // Recover new created files by deleting them $filePath = $file->getPath().'/'.$file->getOriginalName(); if ($this->deleteFromStorage($filePath)) { - $this->service->deleteById($id); + $this->service->deleteById($id, true); $deleted++; array_push($filesRecovered, $id); @@ -173,7 +173,7 @@ class FileOperationController extends Controller if ($candidate !== null) { $path = $dir.'/'.$candidate['name'].'.d'.$candidate['mtime']; if (Trashbin::restore($path, $candidate['name'], $candidate['mtime']) !== false) { - $this->service->deleteById($id); + $this->service->deleteById($id, true); $recovered++; array_push($filesRecovered, $id); @@ -186,7 +186,7 @@ class FileOperationController extends Controller } break; case Monitor::RENAME: - $this->service->deleteById($id); + $this->service->deleteById($id, true); $deleted++; array_push($filesRecovered, $id); @@ -195,7 +195,7 @@ class FileOperationController extends Controller // Recover new created files/folders $filePath = $file->getPath().'/'.$file->getOriginalName(); if ($this->deleteFromStorage($filePath)) { - $this->service->deleteById($id); + $this->service->deleteById($id, true); $deleted++; array_push($filesRecovered, $id); @@ -206,7 +206,7 @@ class FileOperationController extends Controller break; default: // All other commands need no recovery - $this->service->deleteById($id); + $this->service->deleteById($id, false); $deleted++; array_push($filesRecovered, $id); diff --git a/lib/Controller/RecoveredFileOperationController.php b/lib/Controller/RecoveredFileOperationController.php new file mode 100644 index 0000000..95a7cf1 --- /dev/null +++ b/lib/Controller/RecoveredFileOperationController.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\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::WRITE: + // 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::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); + + $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 new created files/folders + $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; + 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/Db/FileOperation.php b/lib/Db/FileOperation.php index 09bcedc..18d400b 100644 --- a/lib/Db/FileOperation.php +++ b/lib/Db/FileOperation.php @@ -22,6 +22,7 @@ namespace OCA\RansomwareDetection\Db; use OCP\AppFramework\Db\Entity; +use OCA\RansomwareDetection\Db\RecoveredFileOperation; class FileOperation extends Entity { @@ -86,4 +87,24 @@ class FileOperation extends Entity $this->addType('fileExtensionClass', 'integer'); $this->addType('fileClass', 'integer'); } + + public function toRecoveredFileOperation() { + $recoveredFileOperation = new RecoveredFileOperation(); + $recoveredFileOperation->setUserId($this->getUserId()); + $recoveredFileOperation->setPath($this->getPath()); + $recoveredFileOperation->setOriginalName($this->getOriginalName()); + $recoveredFileOperation->setNewName($this->getNewName()); + $recoveredFileOperation->setType($this->getType()); + $recoveredFileOperation->setMimeType($this->getMimeType()); + $recoveredFileOperation->setSize($this->getSize()); + $recoveredFileOperation->setTimestamp($this->getTimestamp()); + $recoveredFileOperation->setCorrupted($this->getCorrupted()); + $recoveredFileOperation->setCommand($this->getCommand()); + $recoveredFileOperation->setSequence($this->getSequence()); + $recoveredFileOperation->setEntropy($this->getEntropy()); + $recoveredFileOperation->setStandardDeviation($this->getStandardDeviation()); + $recoveredFileOperation->setFileClass($this->getFileClass()); + $recoveredFileOperation->setFileExtensionClass($this->getFileExtensionClass()); + return $recoveredFileOperation; + } } diff --git a/lib/Db/RecoveredFileOperation.php b/lib/Db/RecoveredFileOperation.php new file mode 100644 index 0000000..74dbce7 --- /dev/null +++ b/lib/Db/RecoveredFileOperation.php @@ -0,0 +1,110 @@ +<?php + +/** + * @copyright Copyright (c) 2020 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\Db; + +use OCP\AppFramework\Db\Entity; +use OCA\RansomwareDetection\Db\FileOperation; + +class RecoveredFileOperation extends Entity +{ + /** @var string */ + public $userId; + + /** @var string */ + public $path; + + /** @var string */ + public $originalName; + + /** @var string */ + public $newName; + + /** @var string */ + public $type; + + /** @var string */ + public $mimeType; + + /** @var int */ + public $size; + + /** @var int */ + public $corrupted; + + /** @var string */ + public $timestamp; + + /** @var int */ + public $command; + + /** @var int */ + public $sequence; + + /** @var float */ + public $entropy; + + /** @var float */ + public $standardDeviation; + + /** @var string */ + public $fileClass; + + /** @var string */ + public $fileExtensionClass; + + /** @var int */ + public $suspicionClass; + + public function __construct() + { + // Add types in constructor + $this->addType('size', 'integer'); + $this->addType('corrupted', 'integer'); + $this->addType('command', 'integer'); + $this->addType('sequence', 'integer'); + $this->addType('entropy', 'float'); + $this->addType('standardDeviation', 'float'); + $this->addType('suspicionClass', 'integer'); + $this->addType('fileExtensionClass', 'integer'); + $this->addType('fileClass', 'integer'); + } + + public function toFileOperation() { + $fileOperation = new FileOperation(); + $fileOperation->setUserId($this->getUserId()); + $fileOperation->setPath($this->getPath()); + $fileOperation->setOriginalName($this->getOriginalName()); + $fileOperation->setNewName($this->getNewName()); + $fileOperation->setType($this->getType()); + $fileOperation->setMimeType($this->getMimeType()); + $fileOperation->setSize($this->getSize()); + $fileOperation->setTimestamp($this->getTimestamp()); + $fileOperation->setCorrupted($this->getCorrupted()); + $fileOperation->setCommand($this->getCommand()); + $fileOperation->setSequence($this->getSequence()); + $fileOperation->setEntropy($this->getEntropy()); + $fileOperation->setStandardDeviation($this->getStandardDeviation()); + $fileOperation->setFileClass($this->getFileClass()); + $fileOperation->setFileExtensionClass($this->getFileExtensionClass()); + return $fileOperation; + } +} diff --git a/lib/Db/RecoveredFileOperationMapper.php b/lib/Db/RecoveredFileOperationMapper.php new file mode 100644 index 0000000..ed6e442 --- /dev/null +++ b/lib/Db/RecoveredFileOperationMapper.php @@ -0,0 +1,156 @@ +<?php + +/** + * @copyright Copyright (c) 2020 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\Db; + +use OCP\IDBConnection; +use OCP\AppFramework\Db\Mapper; + +class RecoveredFileOperationMapper extends Mapper +{ + /** + * @param IDBConnection $db + */ + public function __construct( + IDBConnection $db + ) { + parent::__construct($db, 'ransomware_detection_recovered'); + } + + /** + * Find one by id. + * + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * + * @param int $id + * + * @return Entity + */ + public function find($id, $userId) + { + $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_recovered` '. + 'WHERE `id` = ? AND `user_id` = ?'; + + return $this->findEntity($sql, [$id, $userId]); + } + + /** + * Find one by file name. + * + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * + * @param string $name + * + * @return Entity + */ + public function findOneByFileName($name, $userId) + { + $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_recovered` '. + 'WHERE `original_name` = ? AND `user_id` = ?'; + + return $this->findEntity($sql, [$name, $userId]); + } + + /** + * Find the one with the highest id. + * + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * + * @return Entity + */ + public function findOneWithHighestId($userId) + { + $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_recovered` WHERE `user_id` = ?'. + 'ORDER BY id DESC LIMIT 1'; + + return $this->findEntity($sql, [$userId]); + } + + /** + * Find all. + * + * @param int $limit + * @param int $offset + * + * @return array + */ + public function findAll(array $params = [], $limit = null, $offset = null) + { + $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_recovered` WHERE `user_id` = ?'; + + return $this->findEntities($sql, $params, $limit, $offset); + } + + /** + * Find a sequence by its id. + * + * @param array $params + * @param int $limit + * @param int $offset + * + * @return array + */ + public function findSequenceById(array $params = [], $limit = null, $offset = null) + { + $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_recovered` WHERE `sequence` = ? AND `user_id` = ?'; + + return $this->findEntities($sql, $params, $limit, $offset); + } + + /** + * Delete entity by id. + * + * @param int $id + */ + public function deleteById($id, $userId) + { + $sql = 'DELETE FROM `*PREFIX*ransomware_detection_recovered` WHERE `id` = ? AND `user_id` = ?'; + $stmt = $this->execute($sql, [$id, $userId]); + $stmt->closeCursor(); + } + + /** + * Deletes a sequence of file operations. + * + * @param int $sequence + */ + public function deleteSequenceById($sequence, $userId) + { + $sql = 'DELETE FROM `*PREFIX*ransomware_detection_recovered` WHERE `sequence` = ? AND `user_id` = ?'; + $stmt = $this->execute($sql, [$sequence, $userId]); + $stmt->closeCursor(); + } + + /** + * Delete all entries before $timestamp. + * + * @param int $timestamp + */ + public function deleteFileOperationsBefore($timestamp) + { + $sql = 'DELETE FROM `*PREFIX*ransomware_detection_recovered` WHERE `timestamp` < ?'; + $stmt = $this->execute($sql, [$timestamp]); + $stmt->closeCursor(); + } +} diff --git a/lib/Service/FileOperationService.php b/lib/Service/FileOperationService.php index 049de93..6412b63 100644 --- a/lib/Service/FileOperationService.php +++ b/lib/Service/FileOperationService.php @@ -22,24 +22,31 @@ namespace OCA\RansomwareDetection\Service; use OCA\RansomwareDetection\Db\FileOperationMapper; +use OCA\RansomwareDetection\Db\RecoveredFileOperationMapper; class FileOperationService { /** @var FileOperationMapper */ protected $mapper; + /** @var RecoveredFileOperationMapper */ + protected $recoveredMapper; + /** @var string */ protected $userId; /** * @param FileOperationMapper $mapper + * @param RecoveredFileOperationMapper $recoveredMapper * @param string $userId */ public function __construct( FileOperationMapper $mapper, + RecoveredFileOperationMapper $recoveredMapper, $userId ) { $this->mapper = $mapper; + $this->recoveredMapper = $recoveredMapper; $this->userId = $userId; } @@ -123,9 +130,14 @@ class FileOperationService * * @param int $id */ - public function deleteById($id) + public function deleteById($id, $addToHistory = false) { + $fileOperation = $this->mapper->find($id, $this->userId); $this->mapper->deleteById($id, $this->userId); + if ($addToHistory) { + $this->recoveredMapper->insert($fileOperation->toRecoveredFileOperation()); + } + } /** @@ -135,7 +147,15 @@ class FileOperationService */ public function deleteSequenceById($sequence) { + $params = []; + array_push($params, $sequence); + array_push($params, $this->userId); + + $fileOperations = $this->mapper->findSequenceById($params, null, null); $this->mapper->deleteSequenceById($sequence, $this->userId); + foreach ($fileOperations as $fileOperation) { + $this->recoveredMapper->insert($fileOperation->toRecoveredFileOperation()); + } } /** diff --git a/lib/Service/RecoveredFileOperationService.php b/lib/Service/RecoveredFileOperationService.php new file mode 100644 index 0000000..c983bf4 --- /dev/null +++ b/lib/Service/RecoveredFileOperationService.php @@ -0,0 +1,159 @@ +<?php + +/** + * @copyright Copyright (c) 2020 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\Service; + +use OCA\RansomwareDetection\Db\RecoveredFileOperationMapper; +use OCA\RansomwareDetection\Db\FileOperationMapper; + +class RecoveredFileOperationService +{ + /** @var RecoveredFileOperationMapper */ + protected $recoveredMapper; + + /** @var FileOperationMapper */ + protected $mapper; + + /** @var string */ + protected $userId; + + /** + * @param RecoveredFileOperationMapper $recoveredMapper + * @param FileOperationMapper $mapper + * @param string $userId + */ + public function __construct( + RecoveredFileOperationMapper $recoveredMapper, + FileOperationMapper $mapper, + $userId + ) { + $this->recoveredMapper = $recoveredMapper; + $this->mapper = $mapper; + $this->userId = $userId; + } + + /** + * Find one by the id. + * + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * + * @param int $id + * + * @return Entity + */ + public function find($id) + { + return $this->recoveredMapper->find($id, $this->userId); + } + + /** + * Find one by the file name. + * + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * + * @param string $name + * + * @return Entity + */ + public function findOneByFileName($name) + { + return $this->recoveredMapper->findOneByFileName($name, $this->userId); + } + + /** + * Find one with the highest id. + * + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result + * + * @return Entity + */ + public function findOneWithHighestId() + { + return $this->recoveredMapper->findOneWithHighestId($this->userId); + } + + /** + * Find all. + * + * @param array $params + * @param int $limit + * @param int $offset + * + * @return array + */ + public function findAll(array $params = [], $limit = null, $offset = null) + { + array_push($params, $this->userId); + + return $this->recoveredMapper->findAll($params, $limit, $offset); + } + + /** + * Find sequence by id. + * + * @param array $params + * @param int $limit + * @param int $offset + * + * @return array + */ + public function findSequenceById(array $params = [], $limit = null, $offset = null) + { + array_push($params, $this->userId); + + return $this->recoveredMapper->findSequenceById($params, $limit, $offset); + } + + /** + * Delete one by id. + * + * @param int $id + */ + public function deleteById($id) + { + $fileOperation = $this->recoveredMapper->find($id, $this->userId); + $this->recoveredMapper->deleteById($id, $this->userId); + $this->mapper->insert($fileOperation->toFileOperation()); + } + + /** + * Delete sequence by id. + * + * @param int $sequence + */ + public function deleteSequenceById($sequence) + { + $this->recoveredMapper->deleteSequenceById($sequence, $this->userId); + } + + /** + * Delete all entries before $timestamp. + * + * @param int $timestamp + */ + public function deleteFileOperationsBefore($timestamp) + { + $this->recoveredMapper->deleteFileOperationsBefore($timestamp); + } +} diff --git a/src/views/History.vue b/src/views/History.vue index b973b57..808245b 100644 --- a/src/views/History.vue +++ b/src/views/History.vue @@ -49,10 +49,10 @@ export default { }, computed: { recoverUrl() { - return OC.generateUrl('/apps/ransomware_detection/api/v1/file-operations') + return OC.generateUrl('/apps/ransomware_detection/api/v1/recovered/file-operations') }, fileOperationsUrl() { - return OC.generateUrl('/apps/ransomware_detection/api/v1/file-operation') + return OC.generateUrl('/apps/ransomware_detection/api/v1/recovered/file-operation') } }, methods: { |