diff options
author | Matthias <matthias.held@uni-konstanz.de> | 2020-04-04 22:02:47 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-04 22:02:47 +0300 |
commit | 0a92726e3f3d082ad87101dfb9819e736bcd9df7 (patch) | |
tree | 2745de8ae0a7c039ad9a76d47cc33bf53be1dcf0 | |
parent | b4b724e0af7fbf923be596c64d1cb5a87b299063 (diff) | |
parent | 597ca3e65cc30faca13fead373b0a59e37fb4ccc (diff) |
Merge pull request #30 from undo-ransomware/feature/fix-notes-syncv0.7.1
Fix notes sync
-rw-r--r-- | CHANGELOG.md | 8 | ||||
-rw-r--r-- | appinfo/info.xml | 2 | ||||
-rw-r--r-- | js/utils.js | 2 | ||||
-rw-r--r-- | lib/Analyzer/EntropyAnalyzer.php | 2 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 91 | ||||
-rw-r--r-- | lib/Controller/ScanController.php | 1 | ||||
-rw-r--r-- | lib/Events/FilesEvents.php | 124 | ||||
-rw-r--r-- | lib/FilesHooks.php | 111 | ||||
-rw-r--r-- | lib/Monitor.php | 119 | ||||
-rw-r--r-- | lib/StorageWrapper.php | 259 | ||||
-rw-r--r-- | tests/Unit/AppInfo/ApplicationTest.php | 11 | ||||
-rw-r--r-- | tests/Unit/StorageWrapperTest.php | 203 |
12 files changed, 371 insertions, 562 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0517815..f134279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,17 @@ All notable changes to this project will be documented in this file. +## 0.7.1 + +### Fixed + +- Fix deadlock during sync of notes via the Android Notes app by using post file hooks instead of storage wrapper with pre setup hook. + ## 0.7.0 ### Added -- Nextcloud version 17 support. +- Nextcloud version 18 support. ## 0.6.0 diff --git a/appinfo/info.xml b/appinfo/info.xml index db55c2a..448fc43 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.7.0</version> + <version>0.7.1</version> <licence>agpl</licence> <author mail="matthias.held@uni-konstanz.de">Matthias Held</author> <namespace>RansomwareDetection</namespace> diff --git a/js/utils.js b/js/utils.js index 5820eba..ac11271 100644 --- a/js/utils.js +++ b/js/utils.js @@ -120,7 +120,7 @@ td = $('<td></td>').append($('<p></p>').attr({"title": "READ"}).tooltip({placement: 'top'}).prepend('<span class="fas fa-book fa-fw"></span>')); } else if (fileData.command === 5) { // create - td = $('<td></td>').append($('<p></p>').attr({"title": "CREATE"}).tooltip({placement: 'top'}).prepend('<span class="fas fa-pencil-alt fa-fw"></span>')); + td = $('<td></td>').append($('<p></p>').attr({"title": "CREATE"}).tooltip({placement: 'top'}).prepend('<span class="fas fa-plus-circle fa-fw"></span>')); } else { // error td = $('<td></td>').append($('<p></p>').attr({"title": "ERROR"}).tooltip({placement: 'top'}).prepend('<span class="fas fa-times fa-fw"></span>')); diff --git a/lib/Analyzer/EntropyAnalyzer.php b/lib/Analyzer/EntropyAnalyzer.php index 6aa39ba..9e40b62 100644 --- a/lib/Analyzer/EntropyAnalyzer.php +++ b/lib/Analyzer/EntropyAnalyzer.php @@ -163,7 +163,7 @@ class EntropyAnalyzer { $handle = $node->fopen('r'); if (!$handle) { - $this->logger->debug('calculateEntropyOfFile: Getting data failed.', array('app' => Application::APP_ID)); + $this->logger->warning('calculateEntropyOfFile: Getting data failed.', array('app' => Application::APP_ID)); return 0.0; } diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index dedb5af..9172b33 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,11 +23,15 @@ namespace OCA\RansomwareDetection\AppInfo; use OC\Files\Filesystem; use OCA\RansomwareDetection\Monitor; +use OCA\RansomwareDetection\Events\FilesEvents; +use OCA\RansomwareDetection\FilesHooks; use OCA\RansomwareDetection\Classifier; +use OCA\RansomwareDetection\Analyzer\EntropyAnalyzer; use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer; use OCA\RansomwareDetection\Analyzer\SequenceSizeAnalyzer; use OCA\RansomwareDetection\Analyzer\FileTypeFunnellingAnalyzer; use OCA\RansomwareDetection\Analyzer\EntropyFunnellingAnalyzer; +use OCA\RansomwareDetection\Analyzer\FileCorruptionAnalyzer; use OCA\RansomwareDetection\Analyzer\FileExtensionAnalyzer; use OCA\RansomwareDetection\Entropy\Entropy; use OCA\RansomwareDetection\Notification\Notifier; @@ -36,6 +40,9 @@ use OCA\RansomwareDetection\Connector\Sabre\RequestPlugin; use OCA\RansomwareDetection\Service\FileOperationService; use OCA\RansomwareDetection\Mapper\FileOperationMapper; use OCP\AppFramework\App; +use OCP\App\IAppManager; +use OCP\Files\IRootFolder; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\Storage\IStorage; use OCP\Notification\IManager; use OCP\Util; @@ -44,6 +51,7 @@ use OCP\ILogger; use OCP\IConfig; use OCP\IUserSession; use OCP\ISession; +use OCP\IRequest; class Application extends App { @@ -65,7 +73,7 @@ class Application extends App // services $container->registerService('FileOperationService', function ($c) { return new FileOperationService( - $c->query('FileOperationMapper'), + $c->query(FileOperationMapper::class), $c->query('ServerContainer')->getUserSession()->getUser()->getUID() ); }); @@ -116,6 +124,47 @@ class Application extends App $c->query(EntropyFunnellingAnalyzer::class) ); }); + + $container->registerService('EntropyAnalyzer', function ($c) { + return new EntropyAnalyzer( + $c->query(ILogger::class), + $c->query(IRootFolder::class), + $c->query(Entropy::class), + $c->query('ServerContainer')->getUserSession()->getUser()->getUID() + ); + }); + + $container->registerService('FileCorruptionAnalyzer', function ($c) { + return new FileCorruptionAnalyzer( + $c->query(ILogger::class), + $c->query(IRootFolder::class), + $c->query('ServerContainer')->getUserSession()->getUser()->getUID() + ); + }); + + $container->registerService('Monitor', function ($c) { + return new Monitor( + $c->query(IRequest::class), + $c->query(IConfig::class), + $c->query(ITimeFactory::class), + $c->query(IAppManager::class), + $c->query(ILogger::class), + $c->query(IRootFolder::class), + $c->query(EntropyAnalyzer::class), + $c->query(FileOperationMapper::class), + $c->query(FileExtensionAnalyzer::class), + $c->query(FileCorruptionAnalyzer::class), + $c->query('ServerContainer')->getUserSession()->getUser()->getUID() + ); + }); + + $container->registerService('FilesEvents', function ($c) { + return new FilesEvents( + $c->query(ILogger::class), + $c->query(Monitor::class), + $c->query('ServerContainer')->getUserSession()->getUser()->getUID() + ); + }); } /** @@ -136,42 +185,16 @@ class Application extends App $sequenceAnalyzer = $this->getContainer()->query(SequenceAnalyzer::class); $event->getServer()->addPlugin(new RequestPlugin($logger, $config, $userSession, $session, $service, $notifications, $classifier, $sequenceAnalyzer)); }); - Util::connectHook('OC_Filesystem', 'preSetup', $this, 'addStorageWrapper'); + Util::connectHook('OC_Filesystem', 'post_create', FilesHooks::class, 'onFileCreate'); + // Util::connectHook('OC_Filesystem', 'post_update', FilesHooks::class, 'onFileUpdate'); + Util::connectHook('OC_Filesystem', 'post_rename', FilesHooks::class, 'onFileRename'); + Util::connectHook('OC_Filesystem', 'post_write', FilesHooks::class, 'onFileWrite'); + Util::connectHook('OC_Filesystem', 'post_delete', FilesHooks::class, 'onFileDelete'); + // Util::connectHook('OC_Filesystem', 'post_touch', FilesHooks::class, 'onFileTouch'); + // Util::connectHook('OC_Filesystem', 'post_copy', FilesHooks::class, 'onFileCopy'); $this->registerNotificationNotifier(); } - /** - * @internal - */ - public function addStorageWrapper() - { - Filesystem::addStorageWrapper(self::APP_ID, [$this, 'addStorageWrapperCallback'], -10); - } - - /** - * @internal - * - * @param string $mountPoint - * @param IStorage $storage - * - * @return StorageWrapper|IStorage - */ - public function addStorageWrapperCallback($mountPoint, IStorage $storage) - { - if (!\OC::$CLI && !$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) { - /** @var Monitor $monitor */ - $monitor = $this->getContainer()->query(Monitor::class); - - return new StorageWrapper([ - 'storage' => $storage, - 'mountPoint' => $mountPoint, - 'monitor' => $monitor, - ]); - } - - return $storage; - } - protected function registerNotificationNotifier() { $this->getContainer()->getServer()->getNotificationManager()->registerNotifierService(Notifier::class); diff --git a/lib/Controller/ScanController.php b/lib/Controller/ScanController.php index bd59fcc..a7eca58 100644 --- a/lib/Controller/ScanController.php +++ b/lib/Controller/ScanController.php @@ -165,7 +165,6 @@ class ScanController extends OCSController return new JSONResponse(['status' => 'error', 'message' => 'File does not exist.', 'path' => $trashPath, 'name' => $name, 'mtime' => $timestamp], Http::STATUS_OK); } else { - // wubalubadubdub // 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); diff --git a/lib/Events/FilesEvents.php b/lib/Events/FilesEvents.php new file mode 100644 index 0000000..04ea567 --- /dev/null +++ b/lib/Events/FilesEvents.php @@ -0,0 +1,124 @@ +<?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\Events; + +use OCA\RansomwareDetection\Monitor; +use OCA\RansomwareDetection\AppInfo\Application; +use OCP\ILogger; + +class FilesEvents { + + /** @var string */ + private $userId; + + /** @var ILogger */ + private $logger; + + /** @var Monitor */ + private $monitor; + + + /** + * @param ILogger $logger + * @param Monitor $monitor + * @param string $userId + */ + public function __construct( + ILogger $logger, + Monitor $monitor, + $userId + + ) { + $this->logger = $logger; + $this->monitor = $monitor; + $this->userId = $userId; + } + + /** + * @param array $params + */ + public function onFileUpdate(array $params) { + $this->logger->debug("Updating ".$params['path'].": Params: ".print_r($params, true), ['app' => Application::APP_ID]); + $this->analyze([$params['path']], Monitor::WRITE); + } + + + /** + * @param array $params + */ + public function onFileRename(array $params) { + $this->logger->debug("Renaming ".$params['oldpath']." to ".$params['newpath'].": Params: ".print_r($params, true), ['app' => Application::APP_ID]); + $this->analyze([$params['oldpath'], $params['newpath']], Monitor::RENAME); + } + + /** + * @param array $params + */ + public function onFileCreate(array $params) { + $this->logger->debug("Creating ".$params['path'].": Params: ".print_r($params, true), ['app' => Application::APP_ID]); + $this->analyze([$params['path']], Monitor::CREATE); + } + + /** + * @param array $params + */ + public function onFileWrite(array $params) { + $this->logger->debug("Writing ".$params['path'].": Params: ".print_r($params, true), ['app' => Application::APP_ID]); + $this->analyze([$params['path']], Monitor::WRITE); + } + + /** + * @param array $params + */ + public function onFileDelete(array $params) { + $this->logger->debug("Deleting ".$params['path'].": Params: ".print_r($params, true), ['app' => Application::APP_ID]); + $this->analyze([$params['path']], Monitor::DELETE); + } + + /** + * @param array $params + */ + public function onFileCopy(array $params) { + $this->logger->debug("Copying ".$params['oldpath']." to ".$params['newpath'].": Params: ".print_r($params, true), ['app' => Application::APP_ID]); + $this->analyze([$params['oldpath'], $params['newpath']], Monitor::RENAME); + } + + /** + * @param array $params + */ + public function onFileTouch(array $params) { + $this->logger->debug("Touching ".$params['path'].": Params: ".print_r($params, true), ['app' => Application::APP_ID]); + $this->analyze([$params['path']], Monitor::WRITE); + } + + /** + * Makes it easier to test. + * + * @param IStorage $storage + * @param string $path + * @param int $mode + */ + protected function analyze($path, $mode) + { + return $this->monitor->analyze($path, $mode); + } +}
\ No newline at end of file diff --git a/lib/FilesHooks.php b/lib/FilesHooks.php new file mode 100644 index 0000000..6a74261 --- /dev/null +++ b/lib/FilesHooks.php @@ -0,0 +1,111 @@ +<?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; + +use OCA\RansomwareDetection\AppInfo\Application; +use OCA\RansomwareDetection\Events\FilesEvents; + +class FilesHooks { + + /** + * Retrieve the FilesEvents' Controller. + * + * @return FilesEvents + */ + protected static function getController(): FilesEvents { + $app = new Application(); + + return $app->getContainer() + ->query(FilesEvents::class); + } + + /** + * Hook events: file is updated. + * + * @param array $params + */ + public static function onFileUpdate(array $params) { + self::getController() + ->onFileUpdate($params); + } + + + /** + * Hook events: file is renamed. + * + * @param array $params + */ + public static function onFileRename(array $params) { + self::getController() + ->onFileRename($params); + } + + /** + * Hook events: file is created. + * + * @param array $params + */ + public static function onFileCreate(array $params) { + self::getController() + ->onFileCreate($params); + } + + /** + * Hook events: file is written. + * + * @param array $params + */ + public static function onFileWrite(array $params) { + self::getController() + ->onFileWrite($params); + } + + /** + * Hook events: file is deleted. + * + * @param array $params + */ + public static function onFileDelete(array $params) { + self::getController() + ->onFileDelete($params); + } + + /** + * Hook events: file is touched. + * + * @param array $params + */ + public static function onFileTouch(array $params) { + self::getController() + ->onFileTouch($params); + } + + /** + * Hook events: file is copied. + * + * @param array $params + */ + public static function onFileCopy(array $params) { + self::getController() + ->onFileCopy($params); + } +}
\ No newline at end of file diff --git a/lib/Monitor.php b/lib/Monitor.php index 9c658b9..b1dad47 100644 --- a/lib/Monitor.php +++ b/lib/Monitor.php @@ -89,17 +89,17 @@ class Monitor protected $nestingLevel = 0; /** - * @param IRequest $request - * @param IConfig $config - * @param ITimeFactory $time - * @param IAppManager $appManager - * @param ILogger $logger - * @param IRootFolder $rootFolder - * @param EntropyAnalyzer $entropyAnalyzer - * @param FileOperationMapper $mapper + * @param IRequest $request + * @param IConfig $config + * @param ITimeFactory $time + * @param IAppManager $appManager + * @param ILogger $logger + * @param IRootFolder $rootFolder + * @param EntropyAnalyzer $entropyAnalyzer + * @param FileOperationMapper $mapper * @param FileExtensionAnalyzer $fileExtensionAnalyzer - * @param FileCorruptionAnalyzer $fileCorruptionAnalyzer - * @param string $userId + * @param FileCorruptionAnalyzer $fileCorruptionAnalyzer + * @param string $userId */ public function __construct( IRequest $request, @@ -130,14 +130,15 @@ class Monitor /** * Analyze file. * - * @param IStorage $storage * @param array $paths * @param int $mode */ - public function analyze(IStorage $storage, $paths, $mode) + public function analyze($paths, $mode) { $path = $paths[0]; - if ($this->userId === null || $this->nestingLevel !== 0 || !$this->isUploadedFile($storage, $path) || $this->isCreatingSkeletonFiles()) { + + $storage = $this->rootFolder->getUserFolder($this->userId)->get(dirname($path))->getStorage(); + if ($this->userId === null || $this->nestingLevel !== 0 || /*!$this->isUploadedFile($storage, $path) ||*/ $this->isCreatingSkeletonFiles()) { // check only cloud files and no system files return; } @@ -155,6 +156,8 @@ class Monitor switch ($mode) { case self::RENAME: + $path = $paths[1]; + $this->logger->debug("Rename ".$paths[0]." to ".$paths[1], ['app' => Application::APP_ID]); if (preg_match('/.+\.d[0-9]+/', pathinfo($paths[1])['basename']) > 0) { return; } @@ -162,9 +165,10 @@ class Monitor $this->resetProfindCount(); try { - $userRoot = $this->rootFolder->getUserFolder($this->userId)->getParent(); + $userRoot = $this->rootFolder->getUserFolder($this->userId); $node = $userRoot->get($path); } catch (\OCP\Files\NotFoundException $exception) { + $this->logger->error("File Not Found ".$path, ['app' => Application::APP_ID]); return; } @@ -176,23 +180,21 @@ class Monitor return; } - $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_SHARED); - $this->addFileOperation($paths, $node, self::RENAME); - $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); - $this->nestingLevel--; return; case self::WRITE: + $this->logger->debug("Write ".$path, ['app' => Application::APP_ID]); // reset PROPFIND_COUNT $this->resetProfindCount(); try { - $userRoot = $this->rootFolder->getUserFolder($this->userId)->getParent(); + $userRoot = $this->rootFolder->getUserFolder($this->userId); $node = $userRoot->get($path); } catch (\OCP\Files\NotFoundException $exception) { + $this->logger->error("File Not Found ".$path, ['app' => Application::APP_ID]); return; } @@ -214,13 +216,15 @@ class Monitor return; case self::DELETE: + $this->logger->debug("Delete", ['app' => Application::APP_ID]); // reset PROPFIND_COUNT $this->resetProfindCount(); try { - $userRoot = $this->rootFolder->getUserFolder($this->userId)->getParent(); + $userRoot = $this->rootFolder->getUserFolder($this->userId); $node = $userRoot->get($path); } catch (\OCP\Files\NotFoundException $exception) { + $this->logger->error("File Not Found ".$path, ['app' => Application::APP_ID]); return; } @@ -232,43 +236,53 @@ class Monitor return; } - $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_SHARED); - $this->addFileOperation($paths, $node, self::DELETE); - $node->changeLock(\OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); $this->nestingLevel--; return; case self::CREATE: - // only folders are created - + $this->logger->debug("Create", ['app' => Application::APP_ID]); // reset PROPFIND_COUNT $this->resetProfindCount(); - $fileOperation = new FileOperation(); - $fileOperation->setUserId($this->userId); - $fileOperation->setPath(str_replace('files', '', pathinfo($path)['dirname'])); - $fileOperation->setOriginalName(pathinfo($path)['basename']); - $fileOperation->setType('folder'); - $fileOperation->setMimeType('httpd/unix-directory'); - $fileOperation->setSize(0); - $fileOperation->setTimestamp(time()); - $fileOperation->setCorrupted(false); - $fileOperation->setCommand(self::CREATE); - $sequenceId = $this->config->getUserValue($this->userId, Application::APP_ID, 'sequence_id', 0); - $fileOperation->setSequence($sequenceId); - - // entropy analysis - $fileOperation->setEntropy(0.0); - $fileOperation->setStandardDeviation(0.0); - $fileOperation->setFileClass(EntropyResult::NORMAL); - - // file extension analysis - $fileOperation->setFileExtensionClass(FileExtensionResult::NOT_SUSPICIOUS); - - $this->mapper->insert($fileOperation); - $this->nestingLevel--; + try { + $userRoot = $this->rootFolder->getUserFolder($this->userId); + $node = $userRoot->get($path); + } catch (\OCP\Files\NotFoundException $exception) { + $this->logger->error("File Not Found ".$path, ['app' => Application::APP_ID]); + return; + } + if (!($node instanceof File)) { + + $fileOperation = new FileOperation(); + $fileOperation->setUserId($this->userId); + $fileOperation->setPath(str_replace('files', '', pathinfo($path)['dirname'])); + $fileOperation->setOriginalName(pathinfo($path)['basename']); + $fileOperation->setType('folder'); + $fileOperation->setMimeType('httpd/unix-directory'); + $fileOperation->setSize(0); + $fileOperation->setTimestamp(time()); + $fileOperation->setCorrupted(false); + $fileOperation->setCommand(self::CREATE); + $sequenceId = $this->config->getUserValue($this->userId, Application::APP_ID, 'sequence_id', 0); + $fileOperation->setSequence($sequenceId); + + // entropy analysis + $fileOperation->setEntropy(0.0); + $fileOperation->setStandardDeviation(0.0); + $fileOperation->setFileClass(EntropyResult::NORMAL); + + // file extension analysis + $fileOperation->setFileExtensionClass(FileExtensionResult::NOT_SUSPICIOUS); + + $this->mapper->insert($fileOperation); + $this->nestingLevel--; + } else { + $this->addFileOperation($paths, $node, self::CREATE); + + $this->nestingLevel--; + } return; default: @@ -353,7 +367,11 @@ class Monitor $fullPath = $path; if (property_exists($storage, 'mountPoint')) { /* @var StorageWrapper $storage */ - $fullPath = $storage->mountPoint.$path; + try { + $fullPath = $storage->mountPoint.$path; + } catch (\Exception $ex) { + return true; + } } // ignore transfer files @@ -384,6 +402,7 @@ class Monitor */ private function addFolderOperation($paths, $node, $operation) { + $this->logger->debug("Add folder operation.", ['app' => Application::APP_ID]); $fileOperation = new FileOperation(); $fileOperation->setUserId($this->userId); $fileOperation->setPath(str_replace('files', '', pathinfo($node->getInternalPath())['dirname'])); @@ -420,6 +439,7 @@ class Monitor */ private function addFileOperation($paths, $node, $operation) { + $this->logger->debug("Add file operation.", ['app' => Application::APP_ID]); $fileOperation = new FileOperation(); $fileOperation->setUserId($this->userId); $fileOperation->setPath(str_replace('files', '', pathinfo($node->getInternalPath())['dirname'])); @@ -452,7 +472,6 @@ class Monitor $fileOperation->setStandardDeviation($entropyResult->getStandardDeviation()); $fileOperation->setFileClass($entropyResult->getFileClass()); - $entity = $this->mapper->insert($fileOperation); } } diff --git a/lib/StorageWrapper.php b/lib/StorageWrapper.php deleted file mode 100644 index 17f18e3..0000000 --- a/lib/StorageWrapper.php +++ /dev/null @@ -1,259 +0,0 @@ -<?php - -/** - * @copyright Copyright (c) 2017 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 - * 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; - -use OC\Files\Storage\Wrapper\Wrapper; -use OCP\Files\Storage\IStorage; - -class StorageWrapper extends Wrapper -{ - /** @var Monitor */ - protected $monitor; - - /** @var string */ - public $mountPoint; - - /** - * @param array $parameters - */ - public function __construct( - $parameters - ) { - parent::__construct($parameters); - $this->monitor = $parameters['monitor']; - $this->mountPoint = $parameters['mountPoint']; - } - - /** - * see http://php.net/manual/en/function.mkdir.php. - * - * @param string $path - * - * @return bool - */ - public function mkdir($path) - { - $this->analyze($this, [$path], Monitor::CREATE); - - return $this->storage->mkdir($path); - } - - /** - * see http://php.net/manual/en/function.rmdir.php. - * - * @param string $path - * - * @return bool - */ - public function rmdir($path) - { - $this->analyze($this, [$path], Monitor::DELETE); - - return $this->storage->rmdir($path); - } - - /** - * see http://php.net/manual/en/function.file_get_contents.php. - * - * @param string $path - * - * @return string - */ - public function file_get_contents($path) - { - $this->analyze($this, [$path], Monitor::READ); - - return $this->storage->file_get_contents($path); - } - - /** - * see http://php.net/manual/en/function.file_put_contents.php. - * - * @param string $path - * @param string $data - * - * @return bool - */ - public function file_put_contents($path, $data) - { - $this->analyze($this, [$path], Monitor::WRITE); - - return $this->storage->file_put_contents($path, $data); - } - - /** - * see http://php.net/manual/en/function.unlink.php. - * - * @param string $path - * - * @return bool - */ - public function unlink($path) - { - $this->analyze($this, [$path], Monitor::DELETE); - - return $this->storage->unlink($path); - } - - /** - * see http://php.net/manual/en/function.rename.php. - * - * @param string $path1 - * @param string $path2 - * - * @return bool - */ - public function rename($path1, $path2) - { - $this->analyze($this, [$path1, $path2], Monitor::RENAME); - - return $this->storage->rename($path1, $path2); - } - - /** - * see http://php.net/manual/en/function.copy.php. - * - * @param string $path1 - * @param string $path2 - * - * @return bool - */ - public function copy($path1, $path2) - { - $this->analyze($this, [$path1, $path2], Monitor::WRITE); - - return $this->storage->copy($path1, $path2); - } - - /** - * see http://php.net/manual/en/function.fopen.php. - * - * @param string $path - * @param string $mode - * - * @return resource - */ - public function fopen($path, $mode) - { - $fileMode = Monitor::READ; - switch ($mode) { - case 'r+': - case 'rb+': - case 'w+': - case 'wb+': - case 'x+': - case 'xb+': - case 'a+': - case 'ab+': - case 'w': - case 'wb': - case 'x': - case 'xb': - case 'a': - case 'ab': - $fileMode = Monitor::WRITE; - } - $this->analyze($this, [$path], $fileMode); - - return $this->storage->fopen($path, $mode); - } - - /** - * see http://php.net/manual/en/function.touch.php - * If the backend does not support the operation, false should be returned. - * - * @param string $path - * @param int $mtime - * - * @return bool - */ - public function touch($path, $mtime = null) - { - $this->analyze($this, [$path], Monitor::WRITE); - - return $this->storage->touch($path, $mtime); - } - - /** - * get a cache instance for the storage. - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache - * - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) - { - if (!$storage) { - $storage = $this; - } - $cache = $this->storage->getCache($path, $storage); - - return new CacheWrapper($cache, $storage, $this->monitor); - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * - * @return bool - */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) - { - if ($sourceStorage === $this) { - return $this->copy($sourceInternalPath, $targetInternalPath); - } - $this->analyze($this, [$targetInternalPath], Monitor::WRITE); - - return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * - * @return bool - */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) - { - if ($sourceStorage === $this) { - return $this->rename($sourceInternalPath, $targetInternalPath); - } - $this->analyze($this, [$targetInternalPath], Monitor::WRITE); - - return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } - - /** - * Makes it easier to test. - * - * @param IStorage $storage - * @param string $path - * @param int $mode - */ - protected function analyze(IStorage $storage, $path, $mode) - { - return $this->monitor->analyze($storage, $path, $mode); - } -} diff --git a/tests/Unit/AppInfo/ApplicationTest.php b/tests/Unit/AppInfo/ApplicationTest.php index f838b7f..5f646ca 100644 --- a/tests/Unit/AppInfo/ApplicationTest.php +++ b/tests/Unit/AppInfo/ApplicationTest.php @@ -65,15 +65,4 @@ class ApplicationTest extends TestCase { $this->assertTrue($this->container->query($service) instanceof $expected); } - - public function testAddStorageWrapperCallback() - { - $storage = $this->getMockBuilder('OCP\Files\Storage\IStorage') - ->setConstructorArgs([array()]) - ->getMock(); - - $result = $this->application->addStorageWrapperCallback('mountPoint', $storage); - // Request from CLI, so $results is instanceof IStorage and not StorageWrapper - $this->assertTrue($result instanceof IStorage); - } } diff --git a/tests/Unit/StorageWrapperTest.php b/tests/Unit/StorageWrapperTest.php deleted file mode 100644 index b3b2f23..0000000 --- a/tests/Unit/StorageWrapperTest.php +++ /dev/null @@ -1,203 +0,0 @@ -<?php - -/** - * @copyright Copyright (c) 2017 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\tests\Unit; - -use OCA\RansomwareDetection\Monitor; -use Test\TestCase; - -class StorageWrapperTest extends TestCase -{ - /** @var \OCP\Files\Storage\IStorage|\PHPUnit_Framework_MockObject_MockObject */ - protected $storage; - - /** @var \OCA\RansomwareDetection\Monitor|\PHPUnit_Framework_MockObject_MockObject */ - protected $monitor; - - protected function setUp(): void - { - parent::setUp(); - - $this->storage = $this->getMockBuilder('OCP\Files\Storage\IStorage') - ->setConstructorArgs([array()]) - ->getMock(); - - $this->monitor = $this->getMockBuilder('OCA\RansomwareDetection\Monitor') - ->disableOriginalConstructor() - ->getMock(); - } - - protected function getInstance(array $methods = []) - { - return $this->getMockBuilder('OCA\RansomwareDetection\StorageWrapper') - ->setConstructorArgs([ - [ - 'storage' => $this->storage, - 'mountPoint' => 'mountPoint', - 'monitor' => $this->monitor, - ], - ]) - ->setMethods($methods) - ->getMock(); - } - - public function dataAnalyze() - { - return [ - ['path1', Monitor::READ], - ['path2', Monitor::WRITE], - ['path3', Monitor::RENAME], - ['path4', Monitor::DELETE], - ]; - } - - /** - * @dataProvider dataAnalyze - * - * @param string $path - * @param int $mode - */ - public function testAnalyze($path, $mode) - { - $storage = $this->getInstance(); - - $this->monitor->expects($this->once()) - ->method('analyze') - ->with($storage, $path, $mode); - - $this->monitor->analyze($storage, $path, $mode); - } - - public function dataSinglePath() - { - $tests = []; - $tests[] = ['file_get_contents', 'path1', Monitor::READ, true]; - $tests[] = ['file_get_contents', 'path2', Monitor::READ, false]; - $tests[] = ['unlink', 'path1', Monitor::DELETE, true]; - $tests[] = ['unlink', 'path2', Monitor::DELETE, false]; - $tests[] = ['mkdir', 'path1', Monitor::CREATE, true]; - $tests[] = ['mkdir', 'path2', Monitor::CREATE, false]; - $tests[] = ['rmdir', 'path1', Monitor::DELETE, true]; - $tests[] = ['rmdir', 'path2', Monitor::DELETE, false]; - - return $tests; - } - - /** - * @dataProvider dataSinglePath - * - * @param string $method - * @param string $path - * @param int $mode - * @param bool $return - */ - public function testSinglePath($method, $path, $mode, $return) - { - $storage = $this->getInstance(['analyze']); - - $storage->expects($this->once()) - ->method('analyze') - ->with($storage, [$path], $mode); - - $this->storage->expects($this->once()) - ->method($method) - ->with($path) - ->willReturn($return); - - $this->assertSame($return, $this->invokePrivate($storage, $method, [$path, $mode])); - } - - public function dataDoublePath() - { - $tests = []; - $tests[] = ['rename', 'path1', 'path1', Monitor::RENAME, true]; - $tests[] = ['rename', 'path2', 'path2', Monitor::RENAME, false]; - $tests[] = ['copy', 'path1', 'path1', Monitor::WRITE, true]; - $tests[] = ['copy', 'path2', 'path2', Monitor::WRITE, false]; - - return $tests; - } - - /** - * @dataProvider dataDoublePath - * - * @param string $method - * @param string $path1 - * @param string $path2 - * @param int $mode - * @param bool $return - */ - public function testDoublePath($method, $path1, $path2, $mode, $return) - { - $storage = $this->getInstance(['analyze']); - - $storage->expects($this->once()) - ->method('analyze') - ->with($storage, [$path2, $path1], $mode); - - $this->storage->expects($this->once()) - ->method($method) - ->with($path1, $path2) - ->willReturn($return); - - $this->assertSame($return, $this->invokePrivate($storage, $method, [$path1, $path2, $mode])); - } - - public function dataTwoParameters() - { - $tests = []; - $tests[] = ['file_put_contents', 'path1', 'data', Monitor::WRITE, true]; - $tests[] = ['file_put_contents', 'path1', 'data', Monitor::WRITE, false]; - $tests[] = ['fopen', 'path1', 'z', Monitor::READ, true]; - $tests[] = ['fopen', 'path1', 'z', Monitor::READ, false]; - $tests[] = ['fopen', 'path1', 'x', Monitor::WRITE, true]; - $tests[] = ['fopen', 'path1', 'x', Monitor::WRITE, false]; - $tests[] = ['touch', 'path1', null, Monitor::WRITE, true]; - $tests[] = ['touch', 'path1', null, Monitor::WRITE, false]; - - return $tests; - } - - /** - * @dataProvider dataTwoParameters - * - * @param string $method - * @param string $path - * @param string $param2 - * @param int $mode - * @param bool $return - */ - public function testTwoParameters($method, $path, $param2, $mode, $return) - { - $storage = $this->getInstance(['analyze']); - - $storage->expects($this->once()) - ->method('analyze') - ->with($storage, [$path], $mode); - - $this->storage->expects($this->once()) - ->method($method) - ->with($path, $param2) - ->willReturn($return); - - $this->assertSame($return, $this->invokePrivate($storage, $method, [$path, $param2, $mode])); - } -} |