Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/undo-ransomware/ransomware_detection.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias <matthias.held@uni-konstanz.de>2020-04-04 22:02:47 +0300
committerGitHub <noreply@github.com>2020-04-04 22:02:47 +0300
commit0a92726e3f3d082ad87101dfb9819e736bcd9df7 (patch)
tree2745de8ae0a7c039ad9a76d47cc33bf53be1dcf0
parentb4b724e0af7fbf923be596c64d1cb5a87b299063 (diff)
parent597ca3e65cc30faca13fead373b0a59e37fb4ccc (diff)
Merge pull request #30 from undo-ransomware/feature/fix-notes-syncv0.7.1
Fix notes sync
-rw-r--r--CHANGELOG.md8
-rw-r--r--appinfo/info.xml2
-rw-r--r--js/utils.js2
-rw-r--r--lib/Analyzer/EntropyAnalyzer.php2
-rw-r--r--lib/AppInfo/Application.php91
-rw-r--r--lib/Controller/ScanController.php1
-rw-r--r--lib/Events/FilesEvents.php124
-rw-r--r--lib/FilesHooks.php111
-rw-r--r--lib/Monitor.php119
-rw-r--r--lib/StorageWrapper.php259
-rw-r--r--tests/Unit/AppInfo/ApplicationTest.php11
-rw-r--r--tests/Unit/StorageWrapperTest.php203
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]));
- }
-}