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
path: root/tests
diff options
context:
space:
mode:
authorMatthias Held <ilovemilk@wusa.io>2018-06-18 15:14:17 +0300
committerMatthias Held <ilovemilk@wusa.io>2018-06-18 15:14:17 +0300
commit0d4208bd4934d83654fc3893867b2557546b404a (patch)
treeb6db2416bb0da30e119fdf8ff2120dea7d086481 /tests
parent7a756a94ab887209f7ad7ffc6a01e2d16d01bfd4 (diff)
Add Nextcloud application
Diffstat (limited to 'tests')
-rw-r--r--tests/Integration/AppTest.php93
-rw-r--r--tests/Integration/Db/FileOperationMapperTest.php393
-rw-r--r--tests/Integration/Fixtures/FileOperationFixture.php53
-rw-r--r--tests/Integration/Fixtures/Fixture.php33
-rw-r--r--tests/Unit/Analyzer/EntropyAnalyzerTest.php192
-rw-r--r--tests/Unit/Analyzer/EntropyResultTest.php67
-rw-r--r--tests/Unit/Analyzer/FileCorruptionAnalyzerTest.php156
-rw-r--r--tests/Unit/Analyzer/FileCorruptionResultTest.php45
-rw-r--r--tests/Unit/Analyzer/FileNameAnalyzerTest.php127
-rw-r--r--tests/Unit/Analyzer/FileNameResultTest.php70
-rw-r--r--tests/Unit/Analyzer/FileTypeFunnellingAnalyzerTest.php191
-rw-r--r--tests/Unit/Analyzer/SequenceAnalyzerTest.php144
-rw-r--r--tests/Unit/Analyzer/SequenceOperationsAnalyzerTest.php78
-rw-r--r--tests/Unit/Analyzer/SequenceResultTest.php82
-rw-r--r--tests/Unit/Analyzer/SequenceSizeAnalyzerTest.php83
-rw-r--r--tests/Unit/AppInfo/ApplicationTest.php77
-rw-r--r--tests/Unit/BackgroundJob/CleanUpJobTest.php67
-rw-r--r--tests/Unit/CacheWrapperTest.php89
-rw-r--r--tests/Unit/ClassifierTest.php131
-rw-r--r--tests/Unit/Connector/Sabre/RequestPluginTest.php155
-rw-r--r--tests/Unit/Controller/ApiControllerTest.php345
-rw-r--r--tests/Unit/Controller/RecoverControllerTest.php56
-rw-r--r--tests/Unit/Db/FileOperationMapperTest.php245
-rw-r--r--tests/Unit/Db/FileOperationTest.php93
-rw-r--r--tests/Unit/Db/MapperTestUtility.php227
-rw-r--r--tests/Unit/Entropy/EntropyTest.php81
-rw-r--r--tests/Unit/FileSignatureListTest.php58
-rw-r--r--tests/Unit/MonitorTest.php419
-rw-r--r--tests/Unit/Notification/NotifierTest.php175
-rw-r--r--tests/Unit/Service/FileOperationServiceTest.php253
-rw-r--r--tests/Unit/StorageWrapperTest.php198
-rw-r--r--tests/bootstrap.php19
32 files changed, 4495 insertions, 0 deletions
diff --git a/tests/Integration/AppTest.php b/tests/Integration/AppTest.php
new file mode 100644
index 0000000..5116f93
--- /dev/null
+++ b/tests/Integration/AppTest.php
@@ -0,0 +1,93 @@
+<?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\Integration;
+
+use PHPUnit_Framework_TestCase;
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+use OCA\RansomwareDetection\Tests\Integration\Fixtures\FileOperationFixture;
+use OCP\AppFramework\IAppContainer;
+use OCP\IDBConnection;
+
+/**
+ * This test shows how to make a small Integration Test. Query your class
+ * directly from the container, only pass in mocks if needed and run your tests
+ * against the database.
+ */
+abstract class AppTest extends PHPUnit_Framework_TestCase
+{
+ /** @var FileOperationMapper */
+ protected $fileOperationMapper;
+
+ /** @var IAppContainer */
+ protected $container;
+
+ protected $userId = 'john';
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $app = new Application();
+ $this->container = $app->getContainer();
+
+ // only replace the user id
+ $this->container->registerService('userId', function () {
+ return $this->userId;
+ });
+
+ // set up database layers
+ $this->fileOperationMapper = $this->container->query(FileOperationMapper::class);
+ }
+
+ public function testAppInstalled()
+ {
+ $appManager = $this->container->query('OCP\App\IAppManager');
+ $this->assertTrue($appManager->isInstalled('ransomware_detection'));
+ }
+
+ protected function loadFixtures(array $fixtures = [])
+ {
+ foreach ($fixtures as $fixture) {
+ $fileOperation = new FileOperationFixture($fixture);
+ $this->fileOperationMapper->insert($fileOperation);
+ }
+ }
+
+ protected function clearDatabase($user)
+ {
+ $sql = [
+ 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?',
+ ];
+ $db = $this->container->query(IDBConnection::class);
+ foreach ($sql as $query) {
+ $db->prepare($query)->execute([$this->userId]);
+ }
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->clearDatabase($this->userId);
+ }
+}
diff --git a/tests/Integration/Db/FileOperationMapperTest.php b/tests/Integration/Db/FileOperationMapperTest.php
new file mode 100644
index 0000000..90fd933
--- /dev/null
+++ b/tests/Integration/Db/FileOperationMapperTest.php
@@ -0,0 +1,393 @@
+<?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\Integration\Db;
+
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCA\RansomwareDetection\Tests\Integration\AppTest;
+use OCA\RansomwareDetection\Tests\Integration\Fixtures\FileOperationFixture;
+
+class FileOperationMapperTest extends AppTest
+{
+ public function testFind()
+ {
+ $fileOperation = new FileOperationFixture();
+ $fileOperation = $this->fileOperationMapper->insert($fileOperation);
+
+ $fetched = $this->fileOperationMapper->find($fileOperation->getId(), $this->userId);
+
+ $this->assertInstanceOf(FileOperation::class, $fetched);
+ $this->assertEquals($fileOperation->getOriginalName(), $fetched->getOriginalName());
+ }
+
+ /**
+ * @expectedException OCP\AppFramework\Db\DoesNotExistException
+ */
+ public function testFindNotExisting()
+ {
+ $this->fileOperationMapper->find(0, $this->userId);
+ }
+
+ public function testFindOneByFileName()
+ {
+ $fileOperation = new FileOperationFixture();
+ $fileOperation = $this->fileOperationMapper->insert($fileOperation);
+
+ $fetched = $this->fileOperationMapper->findOneByFileName($fileOperation->getOriginalName(), $this->userId);
+
+ $this->assertInstanceOf(FileOperation::class, $fetched);
+ $this->assertEquals($fileOperation->getOriginalName(), $fetched->getOriginalName());
+ }
+
+ /**
+ * @expectedException OCP\AppFramework\Db\DoesNotExistException
+ */
+ public function testFindOneByFileNameNotExisting()
+ {
+ $this->fileOperationMapper->findOneByFileName('notthedruidwearelookingfor', $this->userId);
+ }
+
+ public function testFindOneWithHighestId()
+ {
+ $fileOperation = new FileOperationFixture();
+ $fileOperation = $this->fileOperationMapper->insert($fileOperation);
+
+ $fetched = $this->fileOperationMapper->findOneWithHighestId($this->userId);
+
+ $this->assertInstanceOf(FileOperation::class, $fetched);
+ $this->assertEquals($fileOperation->getOriginalName(), $fetched->getOriginalName());
+ }
+
+ /**
+ * @expectedException OCP\AppFramework\Db\DoesNotExistException
+ */
+ public function testFindOneWithHighestIdNotExisting()
+ {
+ $this->fileOperationMapper->findOneWithHighestId($this->userId);
+ }
+
+ public function testFindAll()
+ {
+ $fileOperations = [
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat1.gif',
+ 'newName' => 'cat1.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 158000,
+ 'corrupted' => true,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.04,
+ 'fileNameEntropy' => 4.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 3,
+ ],
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat2.gif',
+ 'newName' => 'cat2.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 148000,
+ 'corrupted' => false,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.05,
+ 'fileNameEntropy' => 3.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 1,
+ ],
+ ];
+ $this->loadFixtures($fileOperations);
+ $fetched = $this->fileOperationMapper->findAll([$this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(2, $fetched);
+ $this->assertContainsOnlyInstancesOf(FileOperation::class, $fetched);
+ }
+
+ public function testFindAllFromUserNotExisting()
+ {
+ $fetched = $this->fileOperationMapper->findAll(['notexistinguser']);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(0, $fetched);
+ }
+
+ public function testFindSequenceById()
+ {
+ $fileOperations = [
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat1.gif',
+ 'newName' => 'cat1.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 158000,
+ 'corrupted' => true,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.04,
+ 'fileNameEntropy' => 4.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 3,
+ ],
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat2.gif',
+ 'newName' => 'cat2.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 148000,
+ 'corrupted' => false,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.05,
+ 'fileNameEntropy' => 3.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 1,
+ ],
+ ];
+ $this->loadFixtures($fileOperations);
+ $fetched = $this->fileOperationMapper->findSequenceById([1, $this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(2, $fetched);
+ $this->assertContainsOnlyInstancesOf(FileOperation::class, $fetched);
+ }
+
+ public function testFindSequenceByIdNotFound()
+ {
+ $fileOperations = [
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat1.gif',
+ 'newName' => 'cat1.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 158000,
+ 'corrupted' => true,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.04,
+ 'fileNameEntropy' => 4.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 3,
+ ],
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat2.gif',
+ 'newName' => 'cat2.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 148000,
+ 'corrupted' => false,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.05,
+ 'fileNameEntropy' => 3.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 1,
+ ],
+ ];
+ $this->loadFixtures($fileOperations);
+ $fetched = $this->fileOperationMapper->findSequenceById([2, $this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(0, $fetched);
+ }
+
+ public function testFindSequenceByIdFromUserNotExisting()
+ {
+ $fetched = $this->fileOperationMapper->findSequenceById([1, 'notexistinguser']);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(0, $fetched);
+ }
+
+ public function testDeleteById()
+ {
+ $fileOperations = [
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat1.gif',
+ 'newName' => 'cat1.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 158000,
+ 'corrupted' => true,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.04,
+ 'fileNameEntropy' => 4.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 3,
+ ],
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat2.gif',
+ 'newName' => 'cat2.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 148000,
+ 'corrupted' => false,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.05,
+ 'fileNameEntropy' => 3.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 1,
+ ],
+ ];
+ $this->loadFixtures($fileOperations);
+
+ $fetched = $this->fileOperationMapper->findAll([$this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(2, $fetched);
+ $this->assertContainsOnlyInstancesOf(FileOperation::class, $fetched);
+
+ $this->fileOperationMapper->deleteById($this->fileOperationMapper->findOneWithHighestId($this->userId)->getId(), $this->userId);
+ $fetched = $this->fileOperationMapper->findAll([$this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(1, $fetched);
+ $this->assertContainsOnlyInstancesOf(FileOperation::class, $fetched);
+
+ $this->fileOperationMapper->deleteById($this->fileOperationMapper->findOneWithHighestId($this->userId)->getId(), $this->userId);
+ $fetched = $this->fileOperationMapper->findAll([$this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(0, $fetched);
+ }
+
+ public function testDeleteSequenceById()
+ {
+ $fileOperations = [
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat1.gif',
+ 'newName' => 'cat1.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 158000,
+ 'corrupted' => true,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.04,
+ 'fileNameEntropy' => 4.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 3,
+ ],
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat2.gif',
+ 'newName' => 'cat2.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 148000,
+ 'corrupted' => false,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.05,
+ 'fileNameEntropy' => 3.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 1,
+ ],
+ ];
+ $this->loadFixtures($fileOperations);
+
+ $this->fileOperationMapper->deleteSequenceById(1, $this->userId);
+ $fetched = $this->fileOperationMapper->findAll([$this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(0, $fetched);
+
+ $fileOperations = [
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat1.gif',
+ 'newName' => 'cat1.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 158000,
+ 'corrupted' => true,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.04,
+ 'fileNameEntropy' => 4.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 3,
+ ],
+ [
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat1.gif',
+ 'newName' => 'cat1.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 148000,
+ 'corrupted' => false,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 2,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.05,
+ 'fileNameEntropy' => 3.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 1,
+ ],
+ ];
+ $this->loadFixtures($fileOperations);
+
+ $this->fileOperationMapper->deleteSequenceById(1, $this->userId);
+ $fetched = $this->fileOperationMapper->findAll([$this->userId]);
+ $this->assertInternalType('array', $fetched);
+ $this->assertCount(1, $fetched);
+ $this->assertContainsOnlyInstancesOf(FileOperation::class, $fetched);
+ }
+}
diff --git a/tests/Integration/Fixtures/FileOperationFixture.php b/tests/Integration/Fixtures/FileOperationFixture.php
new file mode 100644
index 0000000..1fe02f7
--- /dev/null
+++ b/tests/Integration/Fixtures/FileOperationFixture.php
@@ -0,0 +1,53 @@
+<?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\Integration\Fixtures;
+
+use OCA\RansomwareDetection\Db\FileOperation;
+
+class FileOperationFixture extends FileOperation
+{
+ use Fixture;
+
+ public function __construct(array $defaults = [])
+ {
+ parent::__construct();
+ $defaults = array_merge([
+ 'userId' => 'john',
+ 'path' => 'files/',
+ 'originalName' => 'cat.gif',
+ 'newName' => 'cat.gif',
+ 'type' => 'file',
+ 'mimeType' => 'image/gif',
+ 'size' => 148000,
+ 'corrupted' => true,
+ 'timestamp' => date_timestamp_get(date_create()),
+ 'command' => 2,
+ 'sequence' => 1,
+ 'entropy' => 7.9123595,
+ 'standardDeviation' => 0.04,
+ 'fileNameEntropy' => 4.1,
+ 'fileClass' => 2,
+ 'fileNameClass' => 3,
+ ], $defaults);
+ $this->fillDefaults($defaults);
+ }
+}
diff --git a/tests/Integration/Fixtures/Fixture.php b/tests/Integration/Fixtures/Fixture.php
new file mode 100644
index 0000000..17d0fea
--- /dev/null
+++ b/tests/Integration/Fixtures/Fixture.php
@@ -0,0 +1,33 @@
+<?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\Integration\Fixtures;
+
+trait Fixture
+{
+ protected function fillDefaults(array $defaults = [])
+ {
+ foreach ($defaults as $key => $value) {
+ $method = 'set'.ucfirst($key);
+ $this->$method($value);
+ }
+ }
+}
diff --git a/tests/Unit/Analyzer/EntropyAnalyzerTest.php b/tests/Unit/Analyzer/EntropyAnalyzerTest.php
new file mode 100644
index 0000000..b630bb2
--- /dev/null
+++ b/tests/Unit/Analyzer/EntropyAnalyzerTest.php
@@ -0,0 +1,192 @@
+<?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\Analyzer;
+
+use OCA\RansomwareDetection\Analyzer\EntropyAnalyzer;
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+use OCA\RansomwareDetection\Entropy\Entropy;
+use OCP\Files\Folder;
+use OCP\Files\File;
+use OCP\Files\IRootFolder;
+use OCP\ILogger;
+use Test\TestCase;
+
+class EntropyAnalyzerTest extends TestCase
+{
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */
+ protected $rootFolder;
+
+ /** @var Entropy|\PHPUnit_Framework_MockObject_MockObject */
+ protected $entropy;
+
+ /** @var string */
+ protected $userId = 'john';
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->logger = $this->createMock(ILogger::class);
+ $this->rootFolder = $this->createMock(IRootFolder::class);
+ $this->entropy = $this->createMock(Entropy::class);
+ }
+
+ public function dataAnalyze()
+ {
+ return [
+ ['entropy' => 6.0, 'standardDeviation' => 0.0, 'class' => EntropyResult::NORMAL],
+ ['entropy' => 7.91, 'standardDeviation' => 0.002, 'class' => EntropyResult::ENCRYPTED],
+ ['entropy' => 7.91, 'standardDeviation' => 0.05, 'class' => EntropyResult::ENCRYPTED],
+ ['entropy' => 7.91, 'standardDeviation' => 0.15, 'class' => EntropyResult::COMPRESSED],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param float $entropy
+ * @param float $standardDeviation
+ * @param int $class
+ */
+ public function testAnalyze($entropy, $standardDeviation, $class)
+ {
+ $entropyAnalyzer = $this->getMockBuilder(EntropyAnalyzer::class)
+ ->setConstructorArgs([$this->logger, $this->rootFolder, $this->entropy, $this->userId])
+ ->setMethods(array('calculateEntropyOfFile', 'createEntropyArrayFromFile', 'calculateStandardDeviationOfEntropy'))
+ ->getMock();
+
+ $entropyAnalyzer->expects($this->any())
+ ->method('calculateEntropyOfFile')
+ ->willReturn($entropy);
+
+ $entropyAnalyzer->expects($this->any())
+ ->method('createEntropyArrayFromFile')
+ ->willReturn([]);
+
+ $entropyAnalyzer->expects($this->any())
+ ->method('calculateStandardDeviationOfEntropy')
+ ->willReturn($standardDeviation);
+
+ $result = $entropyAnalyzer->analyze('test', $this->userId);
+ $this->assertInstanceOf(EntropyResult::class, $result);
+ $this->assertEquals($result->getFileClass(), $class);
+ $this->assertEquals($result->getStandardDeviation(), $standardDeviation);
+ }
+
+ public function testCreateEntropyArrayFromFile()
+ {
+ $entropyAnalyzer = $this->getMockBuilder(EntropyAnalyzer::class)
+ ->setConstructorArgs([$this->logger, $this->rootFolder, $this->entropy, $this->userId])
+ ->setMethods(array('createEntropyArrayFromData'))
+ ->getMock();
+
+ $node = $this->createMock(File::class);
+ $node->method('getContent')
+ ->willReturn('test');
+
+ $userRoot = $this->createMock(Folder::class);
+ $userRoot->method('get')
+ ->willReturn($node);
+
+ $userFolder = $this->createMock(Folder::class);
+ $userFolder->method('getParent')
+ ->willReturn($userRoot);
+
+ $this->rootFolder->method('getUserFolder')
+ ->willReturn($userFolder);
+
+ $entropyAnalyzer->method('createEntropyArrayFromData')
+ ->willReturn([]);
+
+ $this->assertEquals($this->invokePrivate($entropyAnalyzer, 'createEntropyArrayFromFile', [$node, EntropyAnalyzer::BLOCK_SIZE]), []);
+ }
+
+ public function dataCreateEntropyArrayFromData()
+ {
+ return [
+ ['data' => 'asdf', 'blockSize' => 1, 'array' => [7.9, 7.9, 7.9, 7.9]],
+ ['data' => '', 'blockSize' => 1, 'array' => []],
+ ['data' => 'asdf', 'blockSize' => 2, 'array' => [7.9, 7.9]],
+ ['data' => 'a', 'blockSize' => 2, 'array' => []],
+ ['data' => 'aa', 'blockSize' => 2, 'array' => [7.9]],
+ ['data' => 'foobar1', 'blockSize' => 2, 'array' => [7.9, 7.9, 7.9]],
+ ];
+ }
+
+ /**
+ * @dataProvider dataCreateEntropyArrayFromData
+ *
+ * @param string $data
+ * @param int $blockSize
+ * @param array $array
+ */
+ public function testCreateEntropyArrayFromData($data, $blockSize, $array)
+ {
+ $entropyAnalyzer = new EntropyAnalyzer($this->logger, $this->rootFolder, $this->entropy, $this->userId);
+
+ $this->entropy->method('calculateEntropy')
+ ->willReturn(7.9);
+
+ $this->assertEquals($this->invokePrivate($entropyAnalyzer, 'createEntropyArrayFromData', [$data, $blockSize]), $array);
+ }
+
+ public function testCalculateStandardDeviationOfEntropy()
+ {
+ $entropyAnalyzer = new EntropyAnalyzer($this->logger, $this->rootFolder, $this->entropy, $this->userId);
+
+ $this->entropy->method('sd')
+ ->willReturn(0.004);
+
+ $this->assertEquals($this->invokePrivate($entropyAnalyzer, 'calculateStandardDeviationOfEntropy', [[]]), 0.004);
+ }
+
+ public function testCalculateEntropyOfFile()
+ {
+ $entropyAnalyzer = $this->getMockBuilder(EntropyAnalyzer::class)
+ ->setConstructorArgs([$this->logger, $this->rootFolder, $this->entropy, 'john'])
+ ->setMethods(array('calculateEntropy'))
+ ->getMock();
+
+ $node = $this->createMock(File::class);
+ $node->method('getContent')
+ ->willReturn('test');
+
+ $userRoot = $this->createMock(Folder::class);
+ $userRoot->method('get')
+ ->willReturn($node);
+
+ $userFolder = $this->createMock(Folder::class);
+ $userFolder->method('getParent')
+ ->willReturn($userRoot);
+
+ $this->rootFolder->method('getUserFolder')
+ ->willReturn($userFolder);
+
+ $this->entropy->method('calculateEntropy')
+ ->willReturn(4.1);
+
+ $this->assertEquals($this->invokePrivate($entropyAnalyzer, 'calculateEntropyOfFile', [$node]), 4.1);
+ }
+}
diff --git a/tests/Unit/Analyzer/EntropyResultTest.php b/tests/Unit/Analyzer/EntropyResultTest.php
new file mode 100644
index 0000000..658647a
--- /dev/null
+++ b/tests/Unit/Analyzer/EntropyResultTest.php
@@ -0,0 +1,67 @@
+<?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\Analyzer;
+
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+use Test\TestCase;
+
+class EntropyResultTest extends TestCase
+{
+ /** @var EntropyResult */
+ protected $entropyResult;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->entropyResult = new EntropyResult(EntropyResult::ENCRYPTED, 7.99, 0.004);
+ }
+
+ public function testConstruct()
+ {
+ $this->assertEquals($this->entropyResult->getFileClass(), EntropyResult::ENCRYPTED);
+ $this->assertEquals($this->entropyResult->getEntropy(), 7.99);
+ $this->assertEquals($this->entropyResult->getStandardDeviation(), 0.004);
+ }
+
+ public function testFileClass()
+ {
+ $this->entropyResult->setFileClass(EntropyResult::COMPRESSED);
+ $this->assertEquals($this->entropyResult->getFileClass(), EntropyResult::COMPRESSED);
+ $this->entropyResult->setFileClass(EntropyResult::NORMAL);
+ $this->assertEquals($this->entropyResult->getFileClass(), EntropyResult::NORMAL);
+ }
+
+ public function testEntropy()
+ {
+ $this->assertEquals($this->entropyResult->getEntropy(), 7.99);
+ $this->entropyResult->setEntropy(3.00);
+ $this->assertEquals($this->entropyResult->getEntropy(), 3.00);
+ }
+
+ public function testStandardDeviation()
+ {
+ $this->assertEquals($this->entropyResult->getStandardDeviation(), 0.004);
+ $this->entropyResult->setStandardDeviation(3.00);
+ $this->assertEquals($this->entropyResult->getStandardDeviation(), 3.00);
+ }
+}
diff --git a/tests/Unit/Analyzer/FileCorruptionAnalyzerTest.php b/tests/Unit/Analyzer/FileCorruptionAnalyzerTest.php
new file mode 100644
index 0000000..f93d19e
--- /dev/null
+++ b/tests/Unit/Analyzer/FileCorruptionAnalyzerTest.php
@@ -0,0 +1,156 @@
+<?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\Analyzer;
+
+use OCA\RansomwareDetection\Analyzer\FileCorruptionAnalyzer;
+use OCP\Files\Folder;
+use OCP\Files\File;
+use OCP\Files\IRootFolder;
+use OCP\ILogger;
+use Test\TestCase;
+
+class FileCorruptionAnalyzerTest extends TestCase
+{
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */
+ protected $rootFolder;
+
+ /** @var string */
+ protected $userId = 'john';
+
+ /** @var FileCorruptionAnalyzer */
+ protected $fileCorruptionAnalyzer;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->logger = $this->createMock(ILogger::class);
+ $this->rootFolder = $this->createMock(IRootFolder::class);
+
+ $this->fileCorruptionAnalyzer = $this->getMockBuilder(FileCorruptionAnalyzer::class)
+ ->setConstructorArgs([$this->logger, $this->rootFolder, $this->userId])
+ ->setMethods(array('isCorrupted'))
+ ->getMock();
+ }
+
+ public function dataAnalyze()
+ {
+ return [
+ ['isCorrupted' => true],
+ ['isCorrupted' => false],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param bool $isCorrupted
+ */
+ public function testAnalyze($isCorrupted)
+ {
+ $file = $this->createMock(File::class);
+ $file->method('getContent')
+ ->willReturn('test');
+
+ $userRoot = $this->createMock(Folder::class);
+ $userRoot->method('get')
+ ->willReturn($file);
+
+ $userFolder = $this->createMock(Folder::class);
+ $userFolder->method('getParent')
+ ->willReturn($userRoot);
+
+ $this->rootFolder->method('getUserFolder')
+ ->willReturn($userFolder);
+
+ $this->fileCorruptionAnalyzer->method('isCorrupted')
+ ->willReturn($isCorrupted);
+
+ $result = $this->fileCorruptionAnalyzer->analyze($file);
+
+ $this->assertEquals($isCorrupted, $result);
+ }
+
+ public function dataIsCorrupted()
+ {
+ return [
+ ['data' => 'ffff', 'extension' => 'unknown', 'result' => true],
+ ['data' => 'ffd8ffffffff', 'extension' => 'csv', 'result' => true],
+ ['data' => 'ffd8ffffffff', 'extension' => 'jpg', 'result' => false],
+ ];
+ }
+
+ /**
+ * @dataProvider dataIsCorrupted
+ *
+ * @param string $data
+ * @param string $extension
+ * @param bool $result
+ */
+ public function testIsCorrupted($data, $extension, $result)
+ {
+ $isCorrupted = self::getMethod('isCorrupted');
+
+ $node = $this->createMock(File::class);
+
+ $node->expects($this->once())
+ ->method('getContent')
+ ->willReturn(hex2bin($data));
+
+ $node->expects($this->any())
+ ->method('getPath')
+ ->willReturn('/admin/files/file.'.$extension);
+
+ $this->assertEquals($isCorrupted->invokeArgs($this->fileCorruptionAnalyzer, [$node])->isCorrupted(), $result);
+ }
+
+ public function testIsCorruptedCatchException()
+ {
+ $isCorrupted = self::getMethod('isCorrupted');
+
+ $node = $this->createMock(File::class);
+
+ $node->expects($this->once())
+ ->method('getContent')
+ ->will($this->throwException(new \OCP\Files\NotPermittedException()));
+
+ $this->assertEquals($isCorrupted->invokeArgs($this->fileCorruptionAnalyzer, [$node])->isCorrupted(), false);
+ }
+ /**
+ * Get protected method.
+ *
+ * @param string $name
+ *
+ * @return $method
+ */
+ protected static function getMethod($name)
+ {
+ $class = new \ReflectionClass(FileCorruptionAnalyzer::class);
+ $method = $class->getMethod($name);
+ $method->setAccessible(true);
+
+ return $method;
+ }
+}
diff --git a/tests/Unit/Analyzer/FileCorruptionResultTest.php b/tests/Unit/Analyzer/FileCorruptionResultTest.php
new file mode 100644
index 0000000..15947e6
--- /dev/null
+++ b/tests/Unit/Analyzer/FileCorruptionResultTest.php
@@ -0,0 +1,45 @@
+<?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\Analyzer;
+
+use OCA\RansomwareDetection\Analyzer\FileCorruptionResult;
+use Test\TestCase;
+
+class FileCorruptionResultTest extends TestCase
+{
+ /** @var FileResult */
+ protected $fileResult;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->fileResult = new FileCorruptionResult(true);
+ }
+
+ public function testCorrupted()
+ {
+ $this->assertTrue($this->fileResult->isCorrupted());
+ $this->fileResult->setCorrupted(false);
+ $this->assertFalse($this->fileResult->isCorrupted());
+ }
+}
diff --git a/tests/Unit/Analyzer/FileNameAnalyzerTest.php b/tests/Unit/Analyzer/FileNameAnalyzerTest.php
new file mode 100644
index 0000000..c76ce5e
--- /dev/null
+++ b/tests/Unit/Analyzer/FileNameAnalyzerTest.php
@@ -0,0 +1,127 @@
+<?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\Analyzer;
+
+use OCA\RansomwareDetection\Entropy\Entropy;
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use OCA\RansomwareDetection\FileSignatureList;
+use OCA\RansomwareDetection\Analyzer\FileNameAnalyzer;
+use OCP\ILogger;
+use Test\TestCase;
+
+class FileNameAnalyzerTest extends TestCase
+{
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ /** @var Entropy|\PHPUnit_Framework_MockObject_MockObject */
+ protected $entropy;
+
+ /** @var FileNameAnalyzer */
+ protected $fileNameAnalyzer;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->logger = $this->createMock(ILogger::class);
+ $this->entropy = $this->createMock(Entropy::class);
+
+ $this->fileNameAnalyzer = new FileNameAnalyzer($this->logger, $this->entropy);
+ }
+
+ public function dataAnalyze()
+ {
+ return [
+ ['path' => 'file.jpg', 'class' => FileNameResult::NORMAL, 'isFileExtensionKnown' => true, 'entropyOfFileName' => 1.0],
+ ['path' => 'file.unknown', 'class' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'isFileExtensionKnown' => false, 'entropyOfFileName' => 1.0],
+ ['path' => 'file.jpg', 'class' => FileNameResult::SUSPICIOUS_FILE_NAME, 'isFileExtensionKnown' => true, 'entropyOfFileName' => 6.0],
+ ['path' => 'file.unknown', 'class' => FileNameResult::SUSPICIOUS, 'isFileExtensionKnown' => false, 'entropyOfFileName' => 6.0],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param string $path
+ * @param int $class
+ * @param bool $isFileExtensionKnown
+ * @param float $entropyOfFileName
+ */
+ public function testAnalyze($path, $class, $isFileExtensionKnown, $entropyOfFileName)
+ {
+ $this->entropy->method('calculateEntropy')
+ ->willReturn($entropyOfFileName);
+ $result = $this->fileNameAnalyzer->analyze($path);
+ $this->assertInstanceOf(FileNameResult::class, $result);
+ $this->assertEquals($result->getFileNameClass(), $class);
+ $this->assertEquals($result->isFileExtensionKnown(), $isFileExtensionKnown);
+ $this->assertEquals($result->getEntropyOfFileName(), $entropyOfFileName);
+ }
+
+ public function dataIsFileExtensionKnown()
+ {
+ $signatures = FileSignatureList::getSignatures();
+ $extensions = [];
+ foreach ($signatures as $signature) {
+ foreach ($signature['extension'] as $extension) {
+ $extensions[] = $extension;
+ }
+ }
+ $tests = [];
+
+ foreach ($extensions as $extension) {
+ $tests[] = [$extension, true];
+ }
+ $tests[] = ['WNCRY', false];
+
+ return $tests;
+ }
+
+ /**
+ * @dataProvider dataIsFileExtensionKnown
+ *
+ * @param string $extension
+ * @param bool $return
+ */
+ public function testIsFileExtensionKnown($extension, $return)
+ {
+ $this->assertEquals($this->invokePrivate($this->fileNameAnalyzer, 'isFileExtensionKnown', [$extension]), $return);
+ }
+
+ public function testGetFileName()
+ {
+ $this->assertEquals($this->invokePrivate($this->fileNameAnalyzer, 'getFileName', ['/test/filename.extension']), 'filename.extension');
+ }
+
+ public function testGetFileExtension()
+ {
+ $this->assertEquals($this->invokePrivate($this->fileNameAnalyzer, 'getFileExtension', ['filename.extension']), 'extension');
+ }
+
+ public function testCalculateEntropyOfFileName()
+ {
+ $this->entropy->method('calculateEntropy')
+ ->willReturn('6.00');
+ $this->assertEquals($this->invokePrivate($this->fileNameAnalyzer, 'calculateEntropyOfFileName', ['filename.extension']), '6.00');
+ }
+}
diff --git a/tests/Unit/Analyzer/FileNameResultTest.php b/tests/Unit/Analyzer/FileNameResultTest.php
new file mode 100644
index 0000000..5bf5a16
--- /dev/null
+++ b/tests/Unit/Analyzer/FileNameResultTest.php
@@ -0,0 +1,70 @@
+<?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\Analyzer;
+
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use Test\TestCase;
+
+class FileNameResultTest extends TestCase
+{
+ /** @var FileNameResult */
+ protected $fileNameResult;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->fileNameResult = new FileNameResult(FileNameResult::NORMAL, true, 3.0);
+ }
+
+ public function testConstruct()
+ {
+ $this->assertEquals($this->fileNameResult->getFileNameClass(), FileNameResult::NORMAL);
+ $this->assertEquals($this->fileNameResult->isFileExtensionKnown(), true);
+ $this->assertEquals($this->fileNameResult->getEntropyOfFileName(), 3.0);
+ }
+
+ public function testFileNameClass()
+ {
+ $this->fileNameResult->setFileNameClass(FileNameResult::SUSPICIOUS_FILE_EXTENSION);
+ $this->assertEquals($this->fileNameResult->getFileNameClass(), FileNameResult::SUSPICIOUS_FILE_EXTENSION);
+ $this->fileNameResult->setFileNameClass(FileNameResult::SUSPICIOUS_FILE_NAME);
+ $this->assertEquals($this->fileNameResult->getFileNameClass(), FileNameResult::SUSPICIOUS_FILE_NAME);
+ $this->fileNameResult->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $this->assertEquals($this->fileNameResult->getFileNameClass(), FileNameResult::SUSPICIOUS);
+ }
+
+ public function testIsFileExtensionKnown()
+ {
+ $this->fileNameResult->setFileExtensionKnown(true);
+ $this->assertEquals($this->fileNameResult->isFileExtensionKnown(), true);
+ $this->fileNameResult->setFileExtensionKnown(false);
+ $this->assertEquals($this->fileNameResult->isFileExtensionKnown(), false);
+ }
+
+ public function testEntropyOfFileName()
+ {
+ $this->assertEquals($this->fileNameResult->getEntropyOfFileName(), 3.0);
+ $this->fileNameResult->setEntropyOfFileName(3.1);
+ $this->assertEquals($this->fileNameResult->getEntropyOfFileName(), 3.1);
+ }
+}
diff --git a/tests/Unit/Analyzer/FileTypeFunnellingAnalyzerTest.php b/tests/Unit/Analyzer/FileTypeFunnellingAnalyzerTest.php
new file mode 100644
index 0000000..33e4cc0
--- /dev/null
+++ b/tests/Unit/Analyzer/FileTypeFunnellingAnalyzerTest.php
@@ -0,0 +1,191 @@
+<?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\tests\Unit\Analyzer;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Analyzer\FileTypeFunnellingAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use OCA\RansomwareDetection\Db\FileOperation;
+use Test\TestCase;
+
+class FileTypeFunnellingAnalyzerTest extends TestCase
+{
+ /** @var FileTypeFunnellingAnalyzer */
+ protected $fileTypeFunnellingAnalyzer;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->fileTypeFunnellingAnalyzer = new FileTypeFunnellingAnalyzer();
+ }
+
+ public function dataAnalyze()
+ {
+ $fileOperation1 = new FileOperation();
+ $fileOperation1->setCommand(Monitor::WRITE);
+ $fileOperation1->setOriginalName('file.unknown');
+ $fileOperation1->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation1->setCorrupted(false);
+ $fileOperation1->setType('file');
+
+ $fileOperation11 = new FileOperation();
+ $fileOperation11->setCommand(Monitor::WRITE);
+ $fileOperation11->setOriginalName('file.unknown1');
+ $fileOperation11->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation11->setCorrupted(false);
+ $fileOperation11->setType('file');
+
+ $fileOperation12 = new FileOperation();
+ $fileOperation12->setCommand(Monitor::WRITE);
+ $fileOperation12->setOriginalName('file.unknown2');
+ $fileOperation12->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation12->setCorrupted(false);
+ $fileOperation12->setType('file');
+
+ $fileOperation13 = new FileOperation();
+ $fileOperation13->setCommand(Monitor::WRITE);
+ $fileOperation13->setOriginalName('file.unknown3');
+ $fileOperation13->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation13->setCorrupted(false);
+ $fileOperation13->setType('file');
+
+ $fileOperation14 = new FileOperation();
+ $fileOperation14->setCommand(Monitor::WRITE);
+ $fileOperation14->setOriginalName('file.unknown4');
+ $fileOperation14->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation14->setCorrupted(false);
+ $fileOperation14->setType('file');
+
+ $fileOperation15 = new FileOperation();
+ $fileOperation15->setCommand(Monitor::WRITE);
+ $fileOperation15->setOriginalName('file.unknown5');
+ $fileOperation15->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation15->setCorrupted(false);
+ $fileOperation15->setType('file');
+
+ $fileOperation16 = new FileOperation();
+ $fileOperation16->setCommand(Monitor::WRITE);
+ $fileOperation16->setOriginalName('file.unknown6');
+ $fileOperation16->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation16->setCorrupted(false);
+ $fileOperation16->setType('file');
+
+ $fileOperation2 = new FileOperation();
+ $fileOperation2->setCommand(Monitor::WRITE);
+ $fileOperation2->setOriginalName('file.csv');
+ $fileOperation2->setFileNameClass(FileNameResult::NORMAL);
+ $fileOperation2->setCorrupted(false);
+ $fileOperation2->setType('file');
+
+ $fileOperation3 = new FileOperation();
+ $fileOperation3->setCommand(Monitor::WRITE);
+ $fileOperation3->setOriginalName('file.csv');
+ $fileOperation3->setFileNameClass(FileNameResult::NORMAL);
+ $fileOperation3->setCorrupted(true);
+ $fileOperation3->setType('file');
+
+ $fileOperation4 = new FileOperation();
+ $fileOperation4->setCommand(Monitor::RENAME);
+ $fileOperation4->setOriginalName('file.csv');
+ $fileOperation4->setFileNameClass(FileNameResult::NORMAL);
+ $fileOperation4->setCorrupted(true);
+ $fileOperation4->setType('file');
+
+ $fileOperation5 = new FileOperation();
+ $fileOperation5->setCommand(Monitor::DELETE);
+ $fileOperation5->setOriginalName('file.csv');
+ $fileOperation5->setFileNameClass(FileNameResult::NORMAL);
+ $fileOperation5->setCorrupted(true);
+ $fileOperation5->setType('file');
+
+ $fileOperation6 = new FileOperation();
+ $fileOperation6->setCommand(100);
+ $fileOperation6->setOriginalName('file.csv');
+ $fileOperation6->setFileNameClass(FileNameResult::NORMAL);
+ $fileOperation6->setCorrupted(true);
+ $fileOperation6->setType('file');
+
+ $fileOperation7 = new FileOperation();
+ $fileOperation7->setCommand(Monitor::READ);
+ $fileOperation7->setOriginalName('file.unknown');
+ $fileOperation7->setFileNameClass(FileNameResult::SUSPICIOUS);
+ $fileOperation7->setCorrupted(false);
+ $fileOperation7->setType('file');
+ // not a sequence
+ $sequence1 = [$fileOperation1];
+ $sequence2 = [$fileOperation1, $fileOperation1];
+ // a sequence
+ $sequence3 = [$fileOperation1, $fileOperation1, $fileOperation1];
+ $sequence4 = [$fileOperation1, $fileOperation1, $fileOperation1, $fileOperation1];
+ $sequence5 = [$fileOperation1, $fileOperation1, $fileOperation1, $fileOperation1, $fileOperation1];
+ // written files which have all the same unknown file extensions => file type funneling
+ $sequence6 = [$fileOperation1, $fileOperation1, $fileOperation1, $fileOperation1, $fileOperation1, $fileOperation1];
+ // written files which have a distinct unknown file extensions => file type funneling
+ $sequence7 = [$fileOperation11, $fileOperation12, $fileOperation13, $fileOperation14, $fileOperation15, $fileOperation16];
+ // written files have a unknown file extensions => file type funneling
+ $sequence8 = [$fileOperation1, $fileOperation1, $fileOperation1, $fileOperation1, $fileOperation12, $fileOperation13];
+ // all written files have known extensions
+ $sequence9 = [$fileOperation2, $fileOperation2, $fileOperation2, $fileOperation2, $fileOperation2, $fileOperation2];
+ // Only delete and rename => no file type funneling
+ $sequence10 = [$fileOperation4, $fileOperation4, $fileOperation5, $fileOperation5, $fileOperation4, $fileOperation5];
+ // unkown command => no file type funneling
+ $sequence11 = [$fileOperation6, $fileOperation6, $fileOperation6, $fileOperation6, $fileOperation6, $fileOperation6];
+ // some files are known
+ $sequence12 = [$fileOperation1, $fileOperation2, $fileOperation1, $fileOperation1, $fileOperation1, $fileOperation2, $fileOperation1];
+ // all written files have known extensions but are corrupted
+ $sequence13 = [$fileOperation3, $fileOperation3, $fileOperation3, $fileOperation3, $fileOperation3, $fileOperation3, $fileOperation3];
+ // only read access
+ $sequence14 = [$fileOperation7, $fileOperation7, $fileOperation7, $fileOperation7, $fileOperation7, $fileOperation7, $fileOperation7];
+
+ return [
+ ['sequence' => [], 'fileTypeFunnelingClass' => 0],
+ ['sequence' => $sequence1, 'fileTypeFunnelingClass' => 0],
+ ['sequence' => $sequence2, 'fileTypeFunnelingClass' => 0],
+ ['sequence' => $sequence3, 'fileTypeFunnelingClass' => 2],
+ ['sequence' => $sequence4, 'fileTypeFunnelingClass' => 2],
+ ['sequence' => $sequence5, 'fileTypeFunnelingClass' => 2],
+ ['sequence' => $sequence6, 'fileTypeFunnelingClass' => 2],
+ ['sequence' => $sequence7, 'fileTypeFunnelingClass' => 2],
+ ['sequence' => $sequence8, 'fileTypeFunnelingClass' => 1],
+ ['sequence' => $sequence9, 'fileTypeFunnelingClass' => 0],
+ ['sequence' => $sequence10, 'fileTypeFunnelingClass' => 0],
+ ['sequence' => $sequence11, 'fileTypeFunnelingClass' => 0],
+ ['sequence' => $sequence12, 'fileTypeFunnelingClass' => 0],
+ ['sequence' => $sequence13, 'fileTypeFunnelingClass' => 2],
+ ['sequence' => $sequence14, 'fileTypeFunnelingClass' => 0],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param array $sequence
+ * @param int $fileTypeFunnelingClass
+ */
+ public function testAnalyze($sequence, $fileTypeFunnelingClass)
+ {
+ $result = $this->fileTypeFunnellingAnalyzer->analyze($sequence);
+
+ $this->assertEquals($result, $fileTypeFunnelingClass);
+ }
+}
diff --git a/tests/Unit/Analyzer/SequenceAnalyzerTest.php b/tests/Unit/Analyzer/SequenceAnalyzerTest.php
new file mode 100644
index 0000000..892134c
--- /dev/null
+++ b/tests/Unit/Analyzer/SequenceAnalyzerTest.php
@@ -0,0 +1,144 @@
+<?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\tests\Unit\Analyzer;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer;
+use OCA\RansomwareDetection\Analyzer\SequenceSizeAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileTypeFunnellingAnalyzer;
+use OCA\RansomwareDetection\Analyzer\EntropyFunnellingAnalyzer;
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCP\ILogger;
+use Test\TestCase;
+
+class SequenceAnalyzerTest extends TestCase
+{
+ /** @var SequenceAnalyzer */
+ protected $sequenceAnalyzer;
+
+ /** @var FileTypeFunnellingAnalyzer */
+ protected $fileTypeFunnellingAnalyzer;
+
+ /** @var EntropyFunnellingAnalyzer */
+ protected $entropyFunnellingAnalyzer;
+
+ /** @var SequenceSizeAnalyzer */
+ protected $sequenceSizeAnalyzer;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->sequenceSizeAnalyzer = new SequenceSizeAnalyzer();
+ $this->fileTypeFunnellingAnalyzer = new FileTypeFunnellingAnalyzer();
+ $this->entropyFunnellingAnalyzer = new EntropyFunnellingAnalyzer($this->createMock(ILogger::class));
+ $this->sequenceAnalyzer = new SequenceAnalyzer($this->sequenceSizeAnalyzer, $this->fileTypeFunnellingAnalyzer, $this->entropyFunnellingAnalyzer);
+ }
+
+ public function dataAnalyze()
+ {
+ $fileOperation1 = new FileOperation();
+ $fileOperation1->setCommand(Monitor::WRITE);
+ $fileOperation1->setOriginalName('test.csv');
+ $fileOperation1->setSize(123000);
+ $fileOperation1->setType('file');
+ $fileOperation1->setSuspicionClass(Classifier::HIGH_LEVEL_OF_SUSPICION);
+
+ $fileOperation2 = new FileOperation();
+ $fileOperation2->setCommand(Monitor::DELETE);
+ $fileOperation2->setOriginalName('test.csv');
+ $fileOperation2->setSize(123000);
+ $fileOperation2->setType('file');
+ $fileOperation2->setSuspicionClass(Classifier::HIGH_LEVEL_OF_SUSPICION);
+
+ $fileOperation3 = new FileOperation();
+ $fileOperation3->setCommand(Monitor::WRITE);
+ $fileOperation3->setOriginalName('test.csv');
+ $fileOperation3->setSize(123000);
+ $fileOperation3->setType('file');
+ $fileOperation3->setSuspicionClass(Classifier::MIDDLE_LEVEL_OF_SUSPICION);
+
+ $fileOperation4 = new FileOperation();
+ $fileOperation4->setCommand(Monitor::WRITE);
+ $fileOperation4->setOriginalName('test.csv');
+ $fileOperation4->setSize(123000);
+ $fileOperation4->setType('file');
+ $fileOperation4->setSuspicionClass(Classifier::LOW_LEVEL_OF_SUSPICION);
+
+ $fileOperation5 = new FileOperation();
+ $fileOperation5->setCommand(Monitor::WRITE);
+ $fileOperation5->setOriginalName('test.csv');
+ $fileOperation5->setSize(123000);
+ $fileOperation5->setType('file');
+ $fileOperation5->setSuspicionClass(Classifier::NOT_SUSPICIOUS);
+
+ $fileOperation6 = new FileOperation();
+ $fileOperation6->setCommand(Monitor::WRITE);
+ $fileOperation6->setOriginalName('test.csv');
+ $fileOperation6->setSize(123000);
+ $fileOperation6->setType('file');
+ $fileOperation6->setSuspicionClass(Classifier::NO_INFORMATION);
+
+ $fileOperationRead = new FileOperation();
+ $fileOperationRead->setCommand(Monitor::READ);
+ $fileOperationRead->setType('file');
+ $fileOperationRead->setOriginalName('test.csv');
+
+ $fileOperationRename = new FileOperation();
+ $fileOperationRename->setCommand(Monitor::RENAME);
+ $fileOperationRename->setType('file');
+ $fileOperationRename->setOriginalName('test.csv');
+
+ $fileOperationUnknown = new FileOperation();
+ $fileOperationUnknown->setCommand(100);
+ $fileOperationUnknown->setType('file');
+ $fileOperationUnknown->setOriginalName('test.csv');
+
+ //TODO: extend tests
+ return [
+ ['sequence' => [], 'suspicionScore' => 0],
+ ['sequence' => [$fileOperation1], 'suspicionScore' => 1],
+ ['sequence' => [$fileOperation2], 'suspicionScore' => 1],
+ ['sequence' => [$fileOperationRead], 'suspicionScore' => 0],
+ ['sequence' => [$fileOperationRename], 'suspicionScore' => 0],
+ ['sequence' => [$fileOperationUnknown], 'suspicionScore' => 0],
+ ['sequence' => [$fileOperation3], 'suspicionScore' => 0.75],
+ ['sequence' => [$fileOperation4], 'suspicionScore' => 0.5],
+ ['sequence' => [$fileOperation5], 'suspicionScore' => 0],
+ ['sequence' => [$fileOperation6], 'suspicionScore' => 0],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param array $sequence
+ * @param float $suspicionScore
+ */
+ public function testAnalyze($sequence, $suspicionScore)
+ {
+ $result = $this->sequenceAnalyzer->analyze(0, $sequence);
+
+ $this->assertEquals($result->getSuspicionScore(), $suspicionScore);
+ }
+}
diff --git a/tests/Unit/Analyzer/SequenceOperationsAnalyzerTest.php b/tests/Unit/Analyzer/SequenceOperationsAnalyzerTest.php
new file mode 100644
index 0000000..5a7111b
--- /dev/null
+++ b/tests/Unit/Analyzer/SequenceOperationsAnalyzerTest.php
@@ -0,0 +1,78 @@
+<?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\tests\Unit\Analyzer;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Analyzer\SequenceOperationsAnalyzer;
+use OCA\RansomwareDetection\Db\FileOperation;
+use Test\TestCase;
+
+class SequenceOperationsAnalyzerTest extends TestCase
+{
+ /** @var SequenceOperationsAnalyzer */
+ protected $sequenceOperationsAnalyzer;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->sequenceOperationsAnalyzer = new SequenceOperationsAnalyzer();
+ }
+
+ public function dataAnalyze()
+ {
+ $fileOperation1 = new FileOperation();
+ $fileOperation1->setCommand(Monitor::WRITE);
+
+ $fileOperation2 = new FileOperation();
+ $fileOperation2->setCommand(Monitor::DELETE);
+
+ $fileOperation3 = new FileOperation();
+ $fileOperation3->setCommand(Monitor::RENAME);
+
+ $fileOperation4 = new FileOperation();
+ $fileOperation4->setCommand(Monitor::READ);
+
+ return [
+ ['sequence' => [], 'result' => SequenceOperationsAnalyzer::NO_WRITE_AND_DELETE],
+ ['sequence' => [$fileOperation3], 'result' => SequenceOperationsAnalyzer::NO_WRITE_AND_DELETE],
+ ['sequence' => [$fileOperation4], 'result' => SequenceOperationsAnalyzer::NO_WRITE_AND_DELETE],
+ ['sequence' => [$fileOperation3, $fileOperation4], 'result' => SequenceOperationsAnalyzer::NO_WRITE_AND_DELETE],
+ ['sequence' => [$fileOperation1], 'result' => SequenceOperationsAnalyzer::ONLY_WRITE],
+ ['sequence' => [$fileOperation2], 'result' => SequenceOperationsAnalyzer::ONLY_DELETE],
+ ['sequence' => [$fileOperation1, $fileOperation2], 'result' => SequenceOperationsAnalyzer::EQUAL_WRITE_AND_DELETE],
+ ['sequence' => [$fileOperation1, $fileOperation2, $fileOperation2], 'result' => SequenceOperationsAnalyzer::DIFF_WRITE_AND_DELETE],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param array $sequence
+ * @param int $result
+ */
+ public function testAnalyze($sequence, $result)
+ {
+ $ratio = $this->sequenceOperationsAnalyzer->analyze($sequence);
+ $this->assertEquals($ratio, $result);
+ }
+}
diff --git a/tests/Unit/Analyzer/SequenceResultTest.php b/tests/Unit/Analyzer/SequenceResultTest.php
new file mode 100644
index 0000000..adc21dc
--- /dev/null
+++ b/tests/Unit/Analyzer/SequenceResultTest.php
@@ -0,0 +1,82 @@
+<?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\tests\Unit\Analyzer;
+
+use OCA\RansomwareDetection\Analyzer\SequenceResult;
+use Test\TestCase;
+
+class SequenceResultTest extends TestCase
+{
+ /** @var SequenceResult */
+ protected $sequenceResult;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->sequenceResult = new SequenceResult(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ }
+
+ public function testConstruct()
+ {
+ $this->assertEquals($this->sequenceResult->getFileSuspicion(), 0.0);
+ $this->assertEquals($this->sequenceResult->getQuantities(), 0.0);
+ $this->assertEquals($this->sequenceResult->getFileTypeFunnelling(), 0.0);
+ $this->assertEquals($this->sequenceResult->getSuspicionScore(), 0.0);
+ }
+
+ public function testFileSuspicion()
+ {
+ $this->sequenceResult->setFileSuspicion(1.0);
+ $this->assertEquals($this->sequenceResult->getFileSuspicion(1.0), 1.0);
+ }
+
+ public function testQuantities()
+ {
+ $this->sequenceResult->setQuantities(1.0);
+ $this->assertEquals($this->sequenceResult->getQuantities(1.0), 1.0);
+ }
+
+ public function testFileTypeFunnelling()
+ {
+ $this->sequenceResult->setFileTypeFunnelling(1.0);
+ $this->assertEquals($this->sequenceResult->getFileTypeFunnelling(1.0), 1.0);
+ }
+
+ public function testSuspicionScore()
+ {
+ $this->sequenceResult->setSuspicionScore(1.0);
+ $this->assertEquals($this->sequenceResult->getSuspicionScore(1.0), 1.0);
+ }
+
+ public function testSizeWritten()
+ {
+ $this->sequenceResult->setSizeWritten(1.0);
+ $this->assertEquals($this->sequenceResult->getSizeWritten(1.0), 1.0);
+ }
+
+ public function testSizeDeleted()
+ {
+ $this->sequenceResult->setSizeDeleted(1.0);
+ $this->assertEquals($this->sequenceResult->getSizeDeleted(1.0), 1.0);
+ }
+}
diff --git a/tests/Unit/Analyzer/SequenceSizeAnalyzerTest.php b/tests/Unit/Analyzer/SequenceSizeAnalyzerTest.php
new file mode 100644
index 0000000..f419c82
--- /dev/null
+++ b/tests/Unit/Analyzer/SequenceSizeAnalyzerTest.php
@@ -0,0 +1,83 @@
+<?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\tests\Unit\Analyzer;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Analyzer\SequenceSizeAnalyzer;
+use OCA\RansomwareDetection\Db\FileOperation;
+use Test\TestCase;
+
+class SequenceSizeAnalyzerTest extends TestCase
+{
+ /** @var SequenceSizeAnalyzer */
+ protected $sequenceSizeAnalyzer;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->sequenceSizeAnalyzer = new SequenceSizeAnalyzer();
+ }
+
+ public function dataAnalyze()
+ {
+ $fileOperation1 = new FileOperation();
+ $fileOperation1->setCommand(Monitor::WRITE);
+ $fileOperation1->setSize(100);
+
+ $fileOperation2 = new FileOperation();
+ $fileOperation2->setCommand(Monitor::DELETE);
+ $fileOperation2->setSize(100);
+
+ $fileOperation3 = new FileOperation();
+ $fileOperation3->setCommand(Monitor::DELETE);
+ $fileOperation3->setSize(150);
+
+ $fileOperation4 = new FileOperation();
+ $fileOperation4->setCommand(Monitor::RENAME);
+ $fileOperation4->setSize(150);
+
+ $fileOperation5 = new FileOperation();
+ $fileOperation5->setCommand(Monitor::READ);
+ $fileOperation5->setSize(150);
+
+ return [
+ ['sequence' => [$fileOperation1, $fileOperation2], 'result' => SequenceSizeAnalyzer::EQUAL_SIZE],
+ ['sequence' => [$fileOperation1, $fileOperation3], 'result' => SequenceSizeAnalyzer::DIFF_SIZE],
+ ['sequence' => [$fileOperation1, $fileOperation3], 'result' => SequenceSizeAnalyzer::DIFF_SIZE],
+ ['sequence' => [$fileOperation4], 'result' => SequenceSizeAnalyzer::EQUAL_SIZE],
+ ['sequence' => [$fileOperation5], 'result' => SequenceSizeAnalyzer::EQUAL_SIZE],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param array $sequence
+ * @param int $result
+ */
+ public function testAnalyze($sequence, $result)
+ {
+ $ratio = $this->sequenceSizeAnalyzer->analyze($sequence);
+ $this->assertEquals($ratio, $result);
+ }
+}
diff --git a/tests/Unit/AppInfo/ApplicationTest.php b/tests/Unit/AppInfo/ApplicationTest.php
new file mode 100644
index 0000000..81fcad2
--- /dev/null
+++ b/tests/Unit/AppInfo/ApplicationTest.php
@@ -0,0 +1,77 @@
+<?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\AppInfo;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\Notification\Notifier;
+use OCP\Files\Storage\IStorage;
+use OCP\Notification\INotifier;
+use Test\TestCase;
+
+class ApplicationTest extends TestCase
+{
+ /** @var Application */
+ private $application;
+
+ /** @var \OCP\AppFramework\IAppContainer */
+ protected $container;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->application = new Application();
+ $this->container = $this->application->getContainer();
+ }
+
+ public function testContainerAppName()
+ {
+ $this->assertEquals('ransomware_detection', $this->container->getAppName());
+ }
+
+ public function dataContainerQuery()
+ {
+ return [
+ [Notifier::class, INotifier::class],
+ ];
+ }
+
+ /**
+ * @dataProvider dataContainerQuery
+ *
+ * @param string $service
+ * @param string $expected
+ */
+ public function testContainerQuery($service, $expected)
+ {
+ $this->assertTrue($this->container->query($service) instanceof $expected);
+ }
+
+ public function testAddStorageWrapperCallback()
+ {
+ $storage = $this->getMockBuilder('OCP\Files\Storage\IStorage')->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/BackgroundJob/CleanUpJobTest.php b/tests/Unit/BackgroundJob/CleanUpJobTest.php
new file mode 100644
index 0000000..a2e6bc1
--- /dev/null
+++ b/tests/Unit/BackgroundJob/CleanUpJobTest.php
@@ -0,0 +1,67 @@
+<?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\tests\Unit\BackgroundJob;
+
+use OCA\RansomwareDetection\BackgroundJob\CleanUpJob;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCA\RansomwareDetection\Tests\Unit\Db\MapperTestUtility;
+use OCP\IConfig;
+
+class CleanUpJobTest extends MapperTestUtility
+{
+ /** @var FileOperationService|\PHPUnit_Framework_MockObject_MockObject */
+ protected $fileOperationService;
+
+ /** @var CleanUpJob */
+ protected $cleanUpJob;
+
+ /** @var IConfige|\PHPUnit_Framework_MockObject_MockObject */
+ protected $config;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $fileOperationMapper = $this->getMockBuilder(FileOperationMapper::class)
+ ->setMethods(['deleteFileOperationsBefore'])
+ ->setConstructorArgs([$this->db])
+ ->getMock();
+ $this->fileOperationService = $this->getMockBuilder(FileOperationService::class)
+ ->setConstructorArgs([$fileOperationMapper, 'john'])
+ ->getMock();
+ $this->config = $this->createMock(IConfig::class);
+ $this->cleanUpJob = new CleanUpJob($this->fileOperationService, $this->config);
+ }
+
+ /**
+ * Run test.
+ */
+ public function testRun()
+ {
+ $backgroundJob = new CleanUpJob($this->fileOperationService, $this->config);
+ $jobList = $this->getMockBuilder('\OCP\BackgroundJob\IJobList')->getMock();
+ /* @var \OC\BackgroundJob\JobList $jobList */
+ $backgroundJob->execute($jobList);
+ $this->assertTrue(true);
+ }
+}
diff --git a/tests/Unit/CacheWrapperTest.php b/tests/Unit/CacheWrapperTest.php
new file mode 100644
index 0000000..f5dd2fe
--- /dev/null
+++ b/tests/Unit/CacheWrapperTest.php
@@ -0,0 +1,89 @@
+<?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\CacheWrapper;
+use Test\TestCase;
+
+class CacheWrapperTest extends TestCase
+{
+ /** @var \OCP\Files\Cache\ICache|\PHPUnit_Framework_MockObject_MockObject */
+ protected $cache;
+
+ /** @var \OCP\Files\Storage\IStorage|\PHPUnit_Framework_MockObject_MockObject */
+ protected $storage;
+
+ /** @var \OCA\RansomwareDetection\Monitor\Operation|\PHPUnit_Framework_MockObject_MockObject */
+ protected $monitor;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->cache = $this->getMockBuilder('OCP\Files\Cache\ICache')
+ ->getMock();
+ $this->storage = $this->getMockBuilder('OCP\Files\Storage\IStorage')
+ ->getMock();
+ $this->monitor = $this->getMockBuilder('OCA\RansomwareDetection\Monitor')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function dataFormatCacheEntry()
+ {
+ return [
+ ['/admin'],
+ ['/files'],
+ ];
+ }
+
+ /**
+ * @dataProvider dataFormatCacheEntry
+ *
+ * @param string $path
+ */
+ public function testFormatCacheEntry($path)
+ {
+ $formatCacheEntry = self::getMethod('formatCacheEntry');
+ $cacheWrapper = new CacheWrapper($this->cache, $this->storage, $this->monitor);
+
+ $result = $formatCacheEntry->invokeArgs($cacheWrapper, [['path' => $path]]);
+
+ $this->assertEquals($result['path'], $path);
+ }
+
+ /**
+ * Get protected method.
+ *
+ * @param string $name
+ *
+ * @return $method
+ */
+ protected static function getMethod($name)
+ {
+ $class = new \ReflectionClass(CacheWrapper::class);
+ $method = $class->getMethod($name);
+ $method->setAccessible(true);
+
+ return $method;
+ }
+}
diff --git a/tests/Unit/ClassifierTest.php b/tests/Unit/ClassifierTest.php
new file mode 100644
index 0000000..e9b7f38
--- /dev/null
+++ b/tests/Unit/ClassifierTest.php
@@ -0,0 +1,131 @@
+<?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 OCA\RansomwareDetection\Analyzer\EntropyResult;
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCP\ILogger;
+use Test\TestCase;
+
+class ClassifierTest extends TestCase
+{
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ /** @var FileOperationMapper|\PHPUnit_Framework_MockObject_MockObject */
+ protected $fileOperationMapper;
+
+ /** @var FileOperationService|\PHPUnit_Framework_MockObject_MockObject */
+ protected $fileOperationService;
+
+ /** @var Classifier */
+ protected $classifier;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->logger = $this->createMock(ILogger::class);
+ $this->fileOperationMapper = $this->createMock(FileOperationMapper::class);
+ $this->fileOperationService = $this->createMock(FileOperationService::class);
+
+ $this->classifier = new Classifier($this->logger, $this->fileOperationMapper, $this->fileOperationService);
+ }
+
+ public function dataClassifyFile()
+ {
+ return [
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::HIGH_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::LOW_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::LOW_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::WRITE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::READ, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::NO_INFORMATION],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::HIGH_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::LOW_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::LOW_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::DELETE, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::ENCRYPTED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::HIGH_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::LOW_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::LOW_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::COMPRESSED, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::NORMAL, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_EXTENSION, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS_FILE_NAME, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ['command' => Monitor::RENAME, 'fileClass' => EntropyResult::NORMAL, 'fileNameClass' => FileNameResult::SUSPICIOUS, 'suspicionClass' => Classifier::NOT_SUSPICIOUS],
+ ];
+ }
+
+ /**
+ * @dataProvider dataClassifyFile
+ *
+ * @param int $command
+ * @param int $fileClass
+ * @param int $fileNameClass
+ * @param int $suspicionClass
+ */
+ public function testClassifyFile($command, $fileClass, $fileNameClass, $suspicionClass)
+ {
+ $fileOperation = new FileOperation();
+ $fileOperation->setCommand($command);
+ $fileOperation->setFileClass($fileClass);
+ $fileOperation->setFileNameClass($fileNameClass);
+
+ $result = $this->classifier->classifyFile($fileOperation);
+ $this->assertEquals($result->getSuspicionClass(), $suspicionClass);
+ }
+}
diff --git a/tests/Unit/Connector/Sabre/RequestPluginTest.php b/tests/Unit/Connector/Sabre/RequestPluginTest.php
new file mode 100644
index 0000000..e83ba30
--- /dev/null
+++ b/tests/Unit/Connector/Sabre/RequestPluginTest.php
@@ -0,0 +1,155 @@
+<?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\tests\Unit\Connector\Sabre;
+
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\Connector\Sabre\RequestPlugin;
+use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer;
+use OCA\RansomwareDetection\Analyzer\SequenceResult;
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCP\Notification\IManager;
+use OCP\IUserSession;
+use OCP\ISession;
+use OCP\ILogger;
+use OCP\IConfig;
+use OCP\IUser;
+use Sabre\DAV\Server;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Test\TestCase;
+
+class RequestPluginTest extends TestCase
+{
+ /** @var RequestPlugin|\PHPUnit_Framework_MockObject_MockObject */
+ protected $requestPlugin;
+
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+ protected $config;
+
+ /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+ protected $userSession;
+
+ /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
+ protected $session;
+
+ /** @var FileOperationService|\PHPUnit_Framework_MockObject_MockObject */
+ protected $service;
+
+ /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */
+ protected $notifications;
+
+ /** @var Classifier|\PHPUnit_Framework_MockObject_MockObject */
+ protected $classifier;
+
+ /** @var SequenceAnalyzer|\PHPUnit_Framework_MockObject_MockObject */
+ protected $sequenceAnalyzer;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->logger = $this->createMock(ILogger::class);
+ $this->config = $this->getMockBuilder(IConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->userSession = $this->getMockBuilder(IUserSession::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->session = $this->createMock(ISession::class);
+ $this->service = $this->getMockBuilder(FileOperationService::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->notifications = $this->createMock(IManager::class);
+
+ $this->classifier = $this->createMock(Classifier::class);
+
+ $this->sequenceAnalyzer = $this->createMock(SequenceAnalyzer::class);
+ $this->sequenceAnalyzer->method('analyze')
+ ->willReturn(new SequenceResult(0, 0, 0, 0, 0, 0));
+
+ $this->service->expects($this->any())
+ ->method('findSequenceById')
+ ->willReturn(['0']);
+
+ $user = $this->getMockBuilder(IUser::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $user->expects($this->any())
+ ->method('getUID');
+
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+
+ $this->config->expects($this->any())
+ ->method('getAppValue')
+ ->willReturn(3);
+
+ $this->requestPlugin = new RequestPlugin($this->logger, $this->config, $this->userSession, $this->session, $this->service, $this->notifications, $this->classifier, $this->sequenceAnalyzer);
+ }
+
+ public function testInitialize()
+ {
+ $this->requestPlugin->initialize($this->createMock(Server::class));
+ $this->assertTrue(true);
+ }
+
+ public function testBeforeHttpPropFind()
+ {
+ $this->requestPlugin->beforeHttpPropFind($this->createMock(RequestInterface::class), $this->createMock(ResponseInterface::class));
+
+ $this->config->expects($this->any())
+ ->method('getUserValue')
+ ->willReturn(10);
+ $this->requestPlugin->beforeHttpPropFind($this->createMock(RequestInterface::class), $this->createMock(ResponseInterface::class));
+ $this->assertTrue(true);
+ }
+
+ public function testBeforeHttpPut()
+ {
+ $this->requestPlugin->beforeHttpPut($this->createMock(RequestInterface::class), $this->createMock(ResponseInterface::class));
+ $this->assertTrue(true);
+ }
+
+ public function testBeforeHttpDelete()
+ {
+ $this->requestPlugin->beforeHttpDelete($this->createMock(RequestInterface::class), $this->createMock(ResponseInterface::class));
+ $this->assertTrue(true);
+ }
+
+ public function testBeforeHttpGet()
+ {
+ $this->requestPlugin->beforeHttpGet($this->createMock(RequestInterface::class), $this->createMock(ResponseInterface::class));
+ $this->assertTrue(true);
+ }
+
+ public function testBeforeHttpPost()
+ {
+ $this->requestPlugin->beforeHttpPost($this->createMock(RequestInterface::class), $this->createMock(ResponseInterface::class));
+ $this->assertTrue(true);
+ }
+}
diff --git a/tests/Unit/Controller/ApiControllerTest.php b/tests/Unit/Controller/ApiControllerTest.php
new file mode 100644
index 0000000..5201bff
--- /dev/null
+++ b/tests/Unit/Controller/ApiControllerTest.php
@@ -0,0 +1,345 @@
+<?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\Controller;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Analyzer\SequenceAnalyzer;
+use OCA\RansomwareDetection\Analyzer\SequenceResult;
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCA\RansomwareDetection\Controller\ApiController;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\Files\File;
+use OCP\Files\Folder;
+use Test\TestCase;
+
+class ApiControllerTest extends TestCase
+{
+ /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
+ protected $request;
+
+ /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+ protected $userSession;
+
+ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+ protected $config;
+
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ /** @var Classifier|\PHPUnit_Framework_MockObject_MockObject */
+ protected $classifier;
+
+ /** @var Folder|\PHPUnit_Framework_MockObject_MockObject */
+ protected $folder;
+
+ /** @var FileOperationService|\PHPUnit_Framework_MockObject_MockObject */
+ protected $service;
+
+ /** @var SequenceAnalyzer|\PHPUnit_Framework_MockObject_MockObject */
+ protected $sequenceAnalyzer;
+
+ /** @var string */
+ protected $userId = 'john';
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->request = $this->getMockBuilder('OCP\IRequest')
+ ->getMock();
+ $this->userSession = $this->getMockBuilder('OCP\IUserSession')
+ ->getMock();
+ $this->config = $this->getMockBuilder('OCP\IConfig')
+ ->getMock();
+ $this->logger = $this->getMockBuilder('OCP\ILogger')
+ ->getMock();
+ $this->folder = $this->getMockBuilder('OCP\Files\Folder')
+ ->getMock();
+ $connection = $this->getMockBuilder('OCP\IDBConnection')
+ ->getMock();
+ $mapper = $this->getMockBuilder('OCA\RansomwareDetection\Db\FileOperationMapper')
+ ->setConstructorArgs([$connection])
+ ->getMock();
+ $this->service = $this->getMockBuilder('OCA\RansomwareDetection\Service\FileOperationService')
+ ->setConstructorArgs([$mapper, $this->userId])
+ ->getMock();
+ $this->classifier = $this->getMockBuilder('OCA\RansomwareDetection\Classifier')
+ ->setConstructorArgs([$this->logger, $mapper, $this->service])
+ ->getMock();
+ $this->sequenceAnalyzer = $this->createMock(SequenceAnalyzer::class);
+ }
+
+ public function testListFileOperations()
+ {
+ $controller = new ApiController(
+ 'ransomware_detection',
+ $this->request,
+ $this->userSession,
+ $this->config,
+ $this->classifier,
+ $this->logger,
+ $this->folder,
+ $this->service,
+ $this->sequenceAnalyzer,
+ 'john'
+ );
+ $file = $this->getMockBuilder(FileOperation::class)
+ ->setMethods(['getSequence'])
+ ->getMock();
+
+ $sequenceResult = new SequenceResult(0, 0, 0, 0, 0, 0);
+
+ $file->method('getSequence')
+ ->willReturn(1);
+
+ $this->service->method('findAll')
+ ->willReturn([$file]);
+
+ $this->classifier->method('classifyFile');
+ $this->sequenceAnalyzer->method('analyze')
+ ->willReturn($sequenceResult);
+
+ $result = $controller->listFileOperations();
+ $this->assertTrue($result instanceof JSONResponse);
+ $this->assertEquals($result->getStatus(), Http::STATUS_ACCEPTED);
+ }
+
+ public function testDeleteSequence()
+ {
+ $controller = new ApiController(
+ 'ransomware_detection',
+ $this->request,
+ $this->userSession,
+ $this->config,
+ $this->classifier,
+ $this->logger,
+ $this->folder,
+ $this->service,
+ $this->sequenceAnalyzer,
+ 'john'
+ );
+ $this->service->method('deleteSequenceById')
+ ->with(1)
+ ->will($this->returnValue([]));
+
+ $result = $controller->deleteSequence(1);
+ $this->assertTrue($result instanceof JSONResponse);
+ $this->assertEquals($result->getStatus(), Http::STATUS_ACCEPTED);
+ }
+
+ public function dataRecover()
+ {
+ $fileOperationWrite = new FileOperation();
+ $fileOperationWrite->setCommand(Monitor::WRITE);
+ $fileOperationWrite->setPath('/admin/files');
+ $fileOperationWrite->setOriginalName('test.jpg');
+
+ $fileOperationRead = new FileOperation();
+ $fileOperationRead->setCommand(Monitor::READ);
+ $fileOperationRead->setPath('/admin/files');
+ $fileOperationRead->setOriginalName('test.jpg');
+
+ $fileOperationDelete = new FileOperation();
+ $fileOperationDelete->setCommand(Monitor::DELETE);
+ $fileOperationDelete->setPath('/admin/file');
+ $fileOperationDelete->setOriginalName('test.jpg');
+
+ $fileOperationRename = new FileOperation();
+ $fileOperationRename->setCommand(Monitor::RENAME);
+ $fileOperationRename->setPath('/admin/file');
+ $fileOperationRename->setOriginalName('test.jpg');
+
+ return [
+ ['id' => 4, 'fileOperation' => new FileOperation(), 'deleted' => false, 'response' => Http::STATUS_OK],
+ ['id' => 1, 'fileOperation' => $fileOperationRead, 'deleted' => true, 'response' => Http::STATUS_OK],
+ ['id' => 2, 'fileOperation' => $fileOperationRename, 'deleted' => true, 'response' => Http::STATUS_OK],
+ ];
+ }
+
+ /**
+ * @dataProvider dataRecover
+ *
+ * @param array $fileIds
+ * @param FileOperation $fileOperation
+ * @param bool $deleted
+ * @param HttpResponse $response
+ */
+ public function testRecover($fileIds, $fileOperation, $deleted, $response)
+ {
+ $controller = $this->getMockBuilder(ApiController::class)
+ ->setConstructorArgs(['ransomware_detection', $this->request, $this->userSession, $this->config, $this->classifier,
+ $this->logger, $this->folder, $this->service, $this->sequenceAnalyzer, 'john', ])
+ ->setMethods(['deleteFromStorage', 'restoreFromTrashbin', 'getTrashFiles'])
+ ->getMock();
+
+ $controller->expects($this->any())
+ ->method('getTrashFiles')
+ ->willReturn([]);
+
+ $this->service->method('find')
+ ->willReturn($fileOperation);
+
+ $controller->expects($this->any())
+ ->method('deleteFromStorage')
+ ->willReturn($deleted);
+
+ $controller->expects($this->any())
+ ->method('restoreFromTrashbin')
+ ->willReturn($deleted);
+
+ $this->service->method('deleteById');
+
+ $result = $controller->recover($fileIds);
+ $this->assertTrue($result instanceof JSONResponse);
+ $this->assertEquals($result->getStatus(), $response);
+ }
+
+ public function testRecoverMultipleObjectsReturnedException()
+ {
+ $controller = $this->getMockBuilder(ApiController::class)
+ ->setConstructorArgs(['ransomware_detection', $this->request, $this->userSession, $this->config, $this->classifier,
+ $this->logger, $this->folder, $this->service, $this->sequenceAnalyzer, 'john', ])
+ ->setMethods(['getTrashFiles'])
+ ->getMock();
+
+ $fileOperationWrite = new FileOperation();
+ $fileOperationWrite->setCommand(Monitor::WRITE);
+ $fileOperationWrite->setPath('/admin/files');
+ $fileOperationWrite->setOriginalName('test.jpg');
+
+ $controller->expects($this->any())
+ ->method('getTrashFiles')
+ ->willReturn([]);
+
+ $this->service->method('find')
+ ->will($this->throwException(new \OCP\AppFramework\Db\MultipleObjectsReturnedException('test')));
+
+ $result = $controller->recover(1);
+ $this->assertTrue($result instanceof JSONResponse);
+ $this->assertEquals($result->getStatus(), Http::STATUS_BAD_REQUEST);
+ }
+
+ public function testDoesNotExistException()
+ {
+ $controller = new ApiController(
+ 'ransomware_detection',
+ $this->request,
+ $this->userSession,
+ $this->config,
+ $this->classifier,
+ $this->logger,
+ $this->folder,
+ $this->service,
+ $this->sequenceAnalyzer,
+ 'john'
+ );
+
+ $fileOperationWrite = new FileOperation();
+ $fileOperationWrite->setCommand(Monitor::WRITE);
+ $fileOperationWrite->setPath('/admin/files');
+ $fileOperationWrite->setOriginalName('test.jpg');
+
+ $this->service->method('find')
+ ->will($this->throwException(new \OCP\AppFramework\Db\DoesNotExistException('test')));
+
+ $result = $controller->recover(1);
+ $this->assertTrue($result instanceof JSONResponse);
+ $this->assertEquals($result->getStatus(), Http::STATUS_BAD_REQUEST);
+ }
+
+ public function testDeleteFromStorage()
+ {
+ $controller = new ApiController(
+ 'ransomware_detection',
+ $this->request,
+ $this->userSession,
+ $this->config,
+ $this->classifier,
+ $this->logger,
+ $this->folder,
+ $this->service,
+ $this->sequenceAnalyzer,
+ 'john'
+ );
+ $file = $this->createMock(File::class);
+ $file->method('isDeletable')
+ ->willReturn(true);
+
+ $file->method('delete');
+
+ $this->folder->expects($this->once())
+ ->method('get')
+ ->willReturn($file);
+ $this->assertTrue($this->invokePrivate($controller, 'deleteFromStorage', ['/admin/files/test.jpg']));
+ }
+
+ public function testDeleteFromStorageNotPossible()
+ {
+ $controller = new ApiController(
+ 'ransomware_detection',
+ $this->request,
+ $this->userSession,
+ $this->config,
+ $this->classifier,
+ $this->logger,
+ $this->folder,
+ $this->service,
+ $this->sequenceAnalyzer,
+ 'john'
+ );
+ $file = $this->createMock(File::class);
+
+ $file->method('isDeletable')
+ ->willReturn(false);
+
+ $file->method('delete');
+
+ $this->folder->expects($this->once())
+ ->method('get')
+ ->willReturn($file);
+ $this->assertFalse($this->invokePrivate($controller, 'deleteFromStorage', ['/admin/files/test.jpg']));
+ }
+
+ public function testDeleteFromStorageNotFoundException()
+ {
+ $controller = new ApiController(
+ 'ransomware_detection',
+ $this->request,
+ $this->userSession,
+ $this->config,
+ $this->classifier,
+ $this->logger,
+ $this->folder,
+ $this->service,
+ $this->sequenceAnalyzer,
+ 'john'
+ );
+ $folder = $this->createMock(Folder::class);
+
+ $this->folder->expects($this->once())
+ ->method('get')
+ ->will($this->throwException(new \OCP\Files\NotFoundException('test')));
+
+ $this->assertTrue($this->invokePrivate($controller, 'deleteFromStorage', ['/admin/files']));
+ }
+}
diff --git a/tests/Unit/Controller/RecoverControllerTest.php b/tests/Unit/Controller/RecoverControllerTest.php
new file mode 100644
index 0000000..c08d5d9
--- /dev/null
+++ b/tests/Unit/Controller/RecoverControllerTest.php
@@ -0,0 +1,56 @@
+<?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\Controller;
+
+use OCA\RansomwareDetection\Controller\RecoverController;
+use OCP\AppFramework\Http\TemplateResponse;
+use Test\TestCase;
+
+class RecoverControllerTest extends TestCase
+{
+ /** @var RecoverController */
+ protected $controller;
+
+ /** @var string */
+ private $userId = 'john';
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $request = $this->getMockBuilder('OCP\IRequest')->getMock();
+
+ $this->controller = new RecoverController(
+ 'ransomware_detection',
+ $request,
+ $this->userId
+ );
+ }
+
+ public function testIndex()
+ {
+ $result = $this->controller->index();
+
+ $this->assertEquals('index', $result->getTemplateName());
+ $this->assertTrue($result instanceof TemplateResponse);
+ }
+}
diff --git a/tests/Unit/Db/FileOperationMapperTest.php b/tests/Unit/Db/FileOperationMapperTest.php
new file mode 100644
index 0000000..20fb584
--- /dev/null
+++ b/tests/Unit/Db/FileOperationMapperTest.php
@@ -0,0 +1,245 @@
+<?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\Db;
+
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+
+class FileOperationMapperTest extends MapperTestUtility
+{
+ /** @var FileOperationMapper */
+ protected $mapper;
+
+ /** @var array */
+ protected $fileOperations;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->mapper = new FileOperationMapper($this->db);
+
+ // create mock FileOperation
+ $fileOperation1 = new FileOperation();
+ $fileOperation2 = new FileOperation();
+
+ $this->fileOperations = [$fileOperation1, $fileOperation2];
+
+ $this->twoRows = [
+ ['id' => $this->fileOperations[0]->getId()],
+ ['id' => $this->fileOperations[1]->getId()],
+ ];
+ }
+
+ public function testFind()
+ {
+ $userId = 'john';
+ $id = 3;
+ $rows = [['id' => $this->fileOperations[0]->getId()]];
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `id` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$id, $userId], $rows);
+
+ $result = $this->mapper->find($id, $userId);
+ $this->assertEquals($this->fileOperations[0], $result);
+ }
+
+ public function testFindNotFound()
+ {
+ $userId = 'john';
+ $id = 3;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `id` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$id, $userId]);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\DoesNotExistException'
+ );
+ $this->mapper->find($id, $userId);
+ }
+
+ public function testFindMoreThanOneResultFound()
+ {
+ $userId = 'john';
+ $id = 3;
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `id` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$id, $userId], $rows);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\MultipleObjectsReturnedException'
+ );
+ $this->mapper->find($id, $userId);
+ }
+
+ public function testFindOneByFileName()
+ {
+ $userId = 'john';
+ $name = 'test';
+ $rows = [['id' => $this->fileOperations[0]->getId()]];
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `original_name` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$name, $userId], $rows);
+
+ $result = $this->mapper->findOneByFileName($name, $userId);
+ $this->assertEquals($this->fileOperations[0], $result);
+ }
+
+ public function testFindOneByFileNameNotFound()
+ {
+ $userId = 'john';
+ $name = 'test';
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `original_name` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$name, $userId]);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\DoesNotExistException'
+ );
+ $this->mapper->findOneByFileName($name, $userId);
+ }
+
+ public function testFindOneByFileNameMoreThanOneResultFound()
+ {
+ $userId = 'john';
+ $name = 'test';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `original_name` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$name, $userId], $rows);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\MultipleObjectsReturnedException'
+ );
+ $this->mapper->findOneByFileName($name, $userId);
+ }
+
+ public function testFindOneWithHighestId()
+ {
+ $userId = 'john';
+ $rows = [['id' => $this->fileOperations[0]->getId()]];
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?'.
+ 'ORDER BY id DESC LIMIT 1';
+
+ $this->setMapperResult($sql, [$userId], $rows);
+
+ $result = $this->mapper->findOneWithHighestId($userId);
+ $this->assertEquals($this->fileOperations[0], $result);
+ }
+
+ public function testFindOneWithHighestIdNotFound()
+ {
+ $userId = 'john';
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?'.
+ 'ORDER BY id DESC LIMIT 1';
+
+ $this->setMapperResult($sql, [$userId]);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\DoesNotExistException'
+ );
+ $this->mapper->findOneWithHighestId($userId);
+ }
+
+ public function testFindOneWithHighestIdMoreThanOneResultFound()
+ {
+ $userId = 'john';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?'.
+ 'ORDER BY id DESC LIMIT 1';
+
+ $this->setMapperResult($sql, [$userId], $rows);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\MultipleObjectsReturnedException'
+ );
+ $this->mapper->findOneWithHighestId($userId);
+ }
+
+ public function testFindAll()
+ {
+ $userId = 'john';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?';
+
+ $this->setMapperResult($sql, [$userId], $rows);
+ $result = $this->mapper->findAll([$userId]);
+ $this->assertEquals($this->fileOperations, $result);
+ }
+
+ public function testFindSequenceById()
+ {
+ $userId = 'john';
+ $sequence = '1';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `sequence` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$sequence, $userId], $rows);
+ $result = $this->mapper->findSequenceById([$sequence, $userId]);
+ $this->assertEquals($this->fileOperations, $result);
+ }
+
+ public function testDelete()
+ {
+ $fileOperation = new FileOperation();
+ $fileOperation->setId(3);
+
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `id` = ?';
+ $arguments = [$fileOperation->getId()];
+
+ $this->setMapperResult($sql, $arguments, [], null, null, true);
+
+ $this->mapper->delete($fileOperation);
+ }
+
+ public function testDeleteById()
+ {
+ $userId = 'john';
+ $fileOperation = new FileOperation();
+ $fileOperation->setUserId($userId);
+ $fileOperation->setId(3);
+
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `id` = ? AND `user_id` = ?';
+ $arguments = [$fileOperation->getId(), $userId];
+
+ $this->setMapperResult($sql, $arguments, [], null, null, true);
+
+ $this->mapper->deleteById($fileOperation->getId(), $userId);
+ }
+
+ public function testDeleteSequenceById()
+ {
+ $userId = 'john';
+ $fileOperation = new FileOperation();
+ $fileOperation->setId(3);
+ $fileOperation->setUserId($userId);
+ $fileOperation->setSequence(1);
+
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `sequence` = ? AND `user_id` = ?';
+ $arguments = [$fileOperation->getSequence(), $userId];
+
+ $this->setMapperResult($sql, $arguments, [], null, null, true);
+
+ $this->mapper->deleteSequenceById($fileOperation->getSequence(), $userId);
+ }
+}
diff --git a/tests/Unit/Db/FileOperationTest.php b/tests/Unit/Db/FileOperationTest.php
new file mode 100644
index 0000000..9a86ea9
--- /dev/null
+++ b/tests/Unit/Db/FileOperationTest.php
@@ -0,0 +1,93 @@
+<?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\Db;
+
+use OCA\RansomwareDetection\Monitor;
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use OCA\RansomwareDetection\Classifier;
+use OCA\RansomwareDetection\Db\FileOperation;
+use Test\TestCase;
+
+class FileOperationTest extends TestCase
+{
+ /** @var FileOperation */
+ protected $entity;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->entity = new FileOperation();
+ }
+
+ public function dataFileOperation()
+ {
+ $data = [
+ ['field' => 'userId', 'value' => 'john'],
+ ['field' => 'path', 'value' => 'files/'],
+ ['field' => 'originalName', 'value' => 'test.jpg'],
+ ['field' => 'newName', 'value' => 'test.jpg'],
+ ['field' => 'type', 'value' => 'file'],
+ ['field' => 'mimeType', 'value' => 'image/jpg'],
+ ['field' => 'size', 'value' => 123],
+ ['field' => 'corrupted', 'value' => true],
+ ['field' => 'timestamp', 'value' => new \DateTime()],
+ ['field' => 'command', 'value' => Monitor::WRITE],
+ ['field' => 'command', 'value' => Monitor::READ],
+ ['field' => 'command', 'value' => Monitor::RENAME],
+ ['field' => 'command', 'value' => Monitor::DELETE],
+ ['field' => 'sequence', 'value' => 1],
+ ['field' => 'entropy', 'value' => 7.99],
+ ['field' => 'standardDeviation', 'value' => 0.004],
+ ['field' => 'fileNameEntropy', 'value' => 4.0],
+ ['field' => 'fileClass', 'value' => EntropyResult::NORMAL],
+ ['field' => 'fileClass', 'value' => EntropyResult::ENCRYPTED],
+ ['field' => 'fileClass', 'value' => EntropyResult::COMPRESSED],
+ ['field' => 'fileNameClass', 'value' => FileNameResult::NORMAL],
+ ['field' => 'fileNameClass', 'value' => FileNameResult::SUSPICIOUS_FILE_EXTENSION],
+ ['field' => 'fileNameClass', 'value' => FileNameResult::SUSPICIOUS_FILE_NAME],
+ ['field' => 'fileNameClass', 'value' => FileNameResult::SUSPICIOUS],
+ ['field' => 'suspicionClass', 'value' => Classifier::NO_INFORMATION],
+ ['field' => 'suspicionClass', 'value' => Classifier::NOT_SUSPICIOUS],
+ ['field' => 'suspicionClass', 'value' => Classifier::MIDDLE_LEVEL_OF_SUSPICION],
+ ['field' => 'suspicionClass', 'value' => Classifier::LOW_LEVEL_OF_SUSPICION],
+ ['field' => 'suspicionClass', 'value' => Classifier::HIGH_LEVEL_OF_SUSPICION],
+ ];
+
+ return $data;
+ }
+
+ /**
+ * @dataProvider dataFileOperation
+ *
+ * @param string $field
+ * @param mixed $value
+ */
+ public function testFileOperation($field, $value)
+ {
+ $setMethod = 'set'.ucfirst($field);
+ $this->entity->$setMethod($value);
+ $getMethod = 'get'.ucfirst($field);
+ $this->assertEquals($this->entity->$getMethod(), $value);
+ }
+}
diff --git a/tests/Unit/Db/MapperTestUtility.php b/tests/Unit/Db/MapperTestUtility.php
new file mode 100644
index 0000000..fc4bff6
--- /dev/null
+++ b/tests/Unit/Db/MapperTestUtility.php
@@ -0,0 +1,227 @@
+<?php
+
+/**
+ * ownCloud - App Framework.
+ *
+ * @author Bernhard Posselt
+ * @author Matthias Held <matthias.held@uni-konstanz.de>
+ * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\RansomwareDetection\Tests\Unit\Db;
+
+use Test\TestCase;
+
+/**
+ * Simple utility class for testing mappers.
+ */
+abstract class MapperTestUtility extends TestCase
+{
+ protected $db;
+ private $query;
+ private $queryAt;
+ private $prepareAt;
+ private $fetchAt;
+ private $iterators;
+
+ /**
+ * Run this function before the actual test to either set or initialize the
+ * db. After this the db can be accessed by using $this->db.
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->db = $this->getMockBuilder(
+ '\OCP\IDBConnection'
+ )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->query = $this->createMock('\PDOStatement');
+ $this->queryAt = 0;
+ $this->prepareAt = 0;
+ $this->iterators = [];
+ $this->fetchAt = 0;
+ }
+
+ /**
+ * Checks if an array is associative.
+ *
+ * @param array $array
+ *
+ * @return bool true if associative
+ */
+ private function isAssocArray(array $array)
+ {
+ return array_values($array) !== $array;
+ }
+
+ /**
+ * Returns the correct PDO constant based on the value type.
+ *
+ * @param $value
+ *
+ * @return PDO constant
+ */
+ private function getPDOType($value)
+ {
+ switch (gettype($value)) {
+ case 'integer':
+ return \PDO::PARAM_INT;
+ case 'boolean':
+ return \PDO::PARAM_BOOL;
+ default:
+ return \PDO::PARAM_STR;
+ }
+ }
+
+ /**
+ * Create mocks and set expected results for database queries.
+ *
+ * @param string $sql the sql query that you expect to receive
+ * @param array $arguments the expected arguments for the prepare query
+ * method
+ * @param array $returnRows the rows that should be returned for the result
+ * of the database query. If not provided, it wont be assumed that fetch
+ * will be called on the result
+ */
+ protected function setMapperResult(
+ $sql,
+ $arguments = array(),
+ $returnRows = array(),
+ $limit = null,
+ $offset = null,
+ $expectClose = false
+ ) {
+ if ($limit === null && $offset === null) {
+ $this->db->expects($this->at($this->prepareAt))
+ ->method('prepare')
+ ->with($this->equalTo($sql))
+ ->will(($this->returnValue($this->query)));
+ } elseif ($limit !== null && $offset === null) {
+ $this->db->expects($this->at($this->prepareAt))
+ ->method('prepare')
+ ->with($this->equalTo($sql), $this->equalTo($limit))
+ ->will(($this->returnValue($this->query)));
+ } elseif ($limit === null && $offset !== null) {
+ $this->db->expects($this->at($this->prepareAt))
+ ->method('prepare')
+ ->with(
+ $this->equalTo($sql),
+ $this->equalTo(null),
+ $this->equalTo($offset)
+ )
+ ->will(($this->returnValue($this->query)));
+ } else {
+ $this->db->expects($this->at($this->prepareAt))
+ ->method('prepare')
+ ->with(
+ $this->equalTo($sql),
+ $this->equalTo($limit),
+ $this->equalTo($offset)
+ )
+ ->will(($this->returnValue($this->query)));
+ }
+
+ $this->iterators[] = new ArgumentIterator($returnRows);
+
+ $iterators = $this->iterators;
+ $fetchAt = $this->fetchAt;
+
+ $this->query->expects($this->any())
+ ->method('fetch')
+ ->will($this->returnCallback(
+ function () use ($iterators, $fetchAt) {
+ $iterator = $iterators[$fetchAt];
+ $result = $iterator->next();
+
+ if ($result === false) {
+ $fetchAt++;
+ }
+
+ $this->queryAt++;
+
+ return $result;
+ }
+ ));
+
+ if ($this->isAssocArray($arguments)) {
+ foreach ($arguments as $key => $argument) {
+ $pdoConstant = $this->getPDOType($argument);
+ $this->query->expects($this->at($this->queryAt))
+ ->method('bindValue')
+ ->with(
+ $this->equalTo($key),
+ $this->equalTo($argument),
+ $this->equalTo($pdoConstant)
+ );
+ $this->queryAt++;
+ }
+ } else {
+ $index = 1;
+ foreach ($arguments as $argument) {
+ $pdoConstant = $this->getPDOType($argument);
+ $this->query->expects($this->at($this->queryAt))
+ ->method('bindValue')
+ ->with(
+ $this->equalTo($index),
+ $this->equalTo($argument),
+ $this->equalTo($pdoConstant)
+ );
+ $index++;
+ $this->queryAt++;
+ }
+ }
+
+ $this->query->expects($this->at($this->queryAt))
+ ->method('execute')
+ ->will($this->returnCallback(function ($sql, $p = null, $o = null, $s = null) {
+ }));
+ $this->queryAt++;
+
+ if ($expectClose) {
+ $closing = $this->at($this->queryAt);
+ } else {
+ $closing = $this->any();
+ }
+ $this->query->expects($closing)->method('closeCursor');
+ $this->queryAt++;
+
+ $this->prepareAt++;
+ $this->fetchAt++;
+ }
+}
+
+class ArgumentIterator
+{
+ private $arguments;
+
+ public function __construct($arguments)
+ {
+ $this->arguments = $arguments;
+ }
+
+ public function next()
+ {
+ $result = array_shift($this->arguments);
+ if ($result === null) {
+ return false;
+ } else {
+ return $result;
+ }
+ }
+}
diff --git a/tests/Unit/Entropy/EntropyTest.php b/tests/Unit/Entropy/EntropyTest.php
new file mode 100644
index 0000000..d56fcc8
--- /dev/null
+++ b/tests/Unit/Entropy/EntropyTest.php
@@ -0,0 +1,81 @@
+<?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\Entropy;
+
+use OCA\RansomwareDetection\Entropy\Entropy;
+use OCP\ILogger;
+use Test\TestCase;
+
+class EntropyTest extends TestCase
+{
+ /** @var \OCA\RansomwareDetection\Entropy\Entropy */
+ protected $entropy;
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->logger = $this->createMock(ILogger::class);
+ $this->entropy = new Entropy($this->logger);
+ }
+
+ public function dataCalculateEntropy()
+ {
+ $tests = [];
+ $tests[] = ['foo', 0.918296];
+ $tests[] = ['1234567890', 3.321928];
+
+ return $tests;
+ }
+
+ /**
+ * @dataProvider dataCalculateEntropy
+ *
+ * @param string $data
+ * @param float $entropy
+ */
+ public function testCalculateEntropy($data, $entropy)
+ {
+ $this->assertEquals(number_format($this->entropy->calculateEntropy($data), 6), $entropy);
+ }
+
+ public function dataSd()
+ {
+ $tests = [];
+ $tests[] = [[10, 2, 38, 23, 38, 23, 21], 13.284434];
+
+ return $tests;
+ }
+
+ /**
+ * @dataProvider dataSd
+ *
+ * @param string $data
+ * @param float $entropy
+ */
+ public function testSd($data, $sd)
+ {
+ $this->assertEquals(number_format($this->invokePrivate($this->entropy, 'sd', [$data]), 6), $sd);
+ }
+}
diff --git a/tests/Unit/FileSignatureListTest.php b/tests/Unit/FileSignatureListTest.php
new file mode 100644
index 0000000..b1ab829
--- /dev/null
+++ b/tests/Unit/FileSignatureListTest.php
@@ -0,0 +1,58 @@
+<?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\FileSignatureList;
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+use Test\TestCase;
+
+class FileSignatureListTest extends TestCase
+{
+ public function setUp()
+ {
+ parent::setUp();
+ }
+
+ public function dataIsSignatureKnown()
+ {
+ $tests = [];
+
+ foreach (FileSignatureList::getSignatures() as $signature) {
+ $tests[] = [$signature, true];
+ }
+
+ $tests[] = [['byteSequence' => 'aaa', 'offset' => 0, 'extension' => ['test'], 'mimeType' => [], 'file_class' => EntropyResult::COMPRESSED], false];
+
+ return $tests;
+ }
+
+ /**
+ * @dataProvider dataIsSignatureKnown
+ *
+ * @param array $signature
+ * @param bool $return
+ */
+ public function testIsSignatureKown($signature, $return)
+ {
+ $this->assertEquals(in_array($signature, FileSignatureList::getSignatures()), $return);
+ }
+}
diff --git a/tests/Unit/MonitorTest.php b/tests/Unit/MonitorTest.php
new file mode 100644
index 0000000..6047729
--- /dev/null
+++ b/tests/Unit/MonitorTest.php
@@ -0,0 +1,419 @@
+<?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 OCA\RansomwareDetection\Analyzer\EntropyAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileNameAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileNameResult;
+use OCA\RansomwareDetection\Analyzer\FileCorruptionAnalyzer;
+use OCA\RansomwareDetection\Analyzer\FileCorruptionResult;
+use OCA\RansomwareDetection\Analyzer\EntropyResult;
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Files\File;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\Storage\IStorage;
+use OCP\Notification\IManager;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\IRequest;
+use OCP\ISession;
+use Test\TestCase;
+
+class MonitorTest extends TestCase
+{
+ /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
+ protected $request;
+
+ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+ protected $config;
+
+ /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
+ protected $time;
+
+ /** @var IAppManager|\PHPUnit_Framework_MockObject_MockObject */
+ protected $appManager;
+
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ /** @var IRootFolder|\PHPUnit_Framework_MockObject_MockObject */
+ protected $rootFolder;
+
+ /** @var EntropyAnalyzer|\PHPUnit_Framework_MockObject_MockObject */
+ protected $entropyAnalyzer;
+
+ /** @var FileOperationMapper|\PHPUnit_Framework_MockObject_MockObject */
+ protected $mapper;
+
+ /** @var FileNameAnalyzer|\PHPUnit_Framework_MockObject_MockObject */
+ protected $fileNameAnalyzer;
+
+ /** @var FileCorruptionAnalyzer|\PHPUnit_Framework_MockObject_MockObject */
+ protected $fileCorruptionAnalyzer;
+
+ /** @var string */
+ protected $userId = 'john';
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->request = $this->createMock(IRequest::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->time = $this->createMock(ITimeFactory::class);
+ $this->appManager = $this->createMock(IAppManager::class);
+ $this->logger = $this->createMock(ILogger::class);
+ $this->rootFolder = $this->createMock(IRootFolder::class);
+ $this->entropyAnalyzer = $this->createMock(EntropyAnalyzer::class);
+ $this->mapper = $this->createMock(FileOperationMapper::class);
+ $this->fileNameAnalyzer = $this->createMock(FileNameAnalyzer::class);
+ $this->fileCorruptionAnalyzer = $this->createMock(FileCorruptionAnalyzer::class);
+ }
+
+ public function dataAnalyze()
+ {
+ return [
+ ['paths' => ['files/', 'files/'], 'mode' => Monitor::RENAME, 'userAgent' => true, 'timestamp' => time()],
+ ['paths' => ['/admin/files/test/files.extension', 'files/'], 'mode' => Monitor::RENAME, 'userAgent' => false, 'timestamp' => time()],
+ ['paths' => ['/admin/files/test/files.extension', 'files/'], 'mode' => Monitor::RENAME, 'userAgent' => true, 'timestamp' => time()],
+ ['paths' => ['/admin/files/test/files.extension', 'files/'], 'mode' => Monitor::READ, 'userAgent' => true, 'timestamp' => time()],
+ ['paths' => ['/admin/files/test/files.extension', 'files/'], 'mode' => Monitor::WRITE, 'userAgent' => true, 'timestamp' => time()],
+ ['paths' => ['/admin/files/test/files.extension', 'files/'], 'mode' => Monitor::DELETE, 'userAgent' => true, 'timestamp' => time()],
+ ['paths' => ['/admin/files/test/files.extension', 'files/'], 'mode' => 100, 'userAgent' => true, 'timestamp' => time()],
+ ];
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param array $paths
+ * @param int $mode
+ * @param bool $userAgent
+ * @param int $timestamp
+ */
+ public function testAnalyze($paths, $mode, $userAgent, $timestamp)
+ {
+ $monitor = $this->getMockBuilder(Monitor::class)
+ ->setConstructorArgs([$this->request, $this->config, $this->time,
+ $this->appManager, $this->logger, $this->rootFolder,
+ $this->entropyAnalyzer, $this->mapper, $this->fileNameAnalyzer,
+ $this->fileCorruptionAnalyzer, $this->userId])
+ ->setMethods(['isUploadedFile', 'isCreatingSkeletonFiles', 'classifySequence', 'resetProfindCount', 'triggerAsyncAnalysis'])
+ ->getMock();
+
+ $storage = $this->createMock(IStorage::class);
+
+ $monitor->expects($this->any())
+ ->method('isUploadedFile')
+ ->with($storage, $paths[0])
+ ->willReturn(true);
+
+ $monitor->expects($this->any())
+ ->method('isCreatingSkeletonFiles')
+ ->willReturn(false);
+
+ $monitor->expects($this->any())
+ ->method('classifySequence');
+
+ $monitor->expects($this->any())
+ ->method('resetProfindCount');
+
+ $monitor->expects($this->any())
+ ->method('triggerAsyncAnalysis');
+
+ $entropyResult = new EntropyResult(EntropyResult::COMPRESSED, 7.99, 0.004);
+
+ $this->entropyAnalyzer->method('analyze')
+ ->willReturn($entropyResult);
+
+ $fileNameResult = new FileNameResult(FileNameResult::NORMAL, true, 4.0);
+
+ $this->fileNameAnalyzer->method('analyze')
+ ->willReturn($fileNameResult);
+
+ $this->request->method('isUserAgent')
+ ->willReturn($userAgent);
+
+ $node = $this->createMock(File::class);
+ $node->method('getInternalPath')
+ ->willReturn('/admin/files/test.file');
+
+ $userRoot = $this->createMock(Folder::class);
+ $userRoot->method('get')
+ ->willReturn($node);
+
+ $folder = $this->createMock(Folder::class);
+ $folder->method('getParent')
+ ->willReturn($userRoot);
+
+ $this->rootFolder->method('getUserFolder')
+ ->willReturn($folder);
+
+ $fileOperation = new FileOperation();
+ $fileOperation->setTimestamp($timestamp);
+
+ $entity = new FileOperation();
+ $entity->id = 1;
+
+ $this->mapper->method('insert')
+ ->willReturn($entity);
+
+ $fileCorruptionResult = new FileCorruptionResult(true);
+ $this->fileCorruptionAnalyzer->method('analyze')
+ ->willReturn($fileCorruptionResult);
+
+ $monitor->analyze($storage, $paths, $mode);
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @dataProvider dataAnalyze
+ *
+ * @param array $paths
+ * @param int $mode
+ * @param bool $userAgent
+ * @param int $timestamp
+ */
+ public function testAnalyzeNotFoundException($paths, $mode, $userAgent, $timestamp)
+ {
+ $monitor = $this->getMockBuilder(Monitor::class)
+ ->setConstructorArgs([$this->request, $this->config, $this->time,
+ $this->appManager, $this->logger, $this->rootFolder,
+ $this->entropyAnalyzer, $this->mapper, $this->fileNameAnalyzer,
+ $this->fileCorruptionAnalyzer, $this->userId])
+ ->setMethods(['isUploadedFile', 'isCreatingSkeletonFiles', 'triggerAsyncAnalysis', 'resetProfindCount'])
+ ->getMock();
+
+ $storage = $this->createMock(IStorage::class);
+
+ $monitor->expects($this->any())
+ ->method('isUploadedFile')
+ ->with($storage, $paths[0])
+ ->willReturn(true);
+
+ $monitor->expects($this->any())
+ ->method('isCreatingSkeletonFiles')
+ ->willReturn(false);
+
+ $this->request->method('isUserAgent')
+ ->willReturn($userAgent);
+
+ $monitor->expects($this->any())
+ ->method('resetProfindCount');
+
+ $monitor->expects($this->any())
+ ->method('triggerAsyncAnalysis');
+
+ $node = $this->createMock(Folder::class);
+ $node->method('getInternalPath')
+ ->willReturn('/admin/files/test.file');
+
+ $userRoot = $this->createMock(Folder::class);
+ $userRoot->method('get')
+ ->willReturn($node);
+
+ $folder = $this->createMock(Folder::class);
+ $folder->method('getParent')
+ ->willReturn($userRoot);
+
+ $this->rootFolder->method('getUserFolder')
+ ->willReturn($folder);
+
+ $fileOperation = new FileOperation();
+ $fileOperation->setTimestamp($timestamp);
+
+ $entity = new FileOperation();
+ $entity->id = 1;
+
+ $this->mapper->method('insert')
+ ->willReturn($entity);
+
+ $fileCorruptionResult = new FileCorruptionResult(true);
+ $this->fileCorruptionAnalyzer->method('analyze')
+ ->willReturn($fileCorruptionResult);
+
+ $monitor->analyze($storage, $paths, $mode);
+ $this->assertTrue(true);
+ }
+
+ public function dataGetFileSize()
+ {
+ return [
+ ['path' => '/files_trashbin/test.jpg', 'size' => 10],
+ ['path' => '/files/test.jpg', 'size' => 10],
+ ];
+ }
+
+ /**
+ * @dataProvider dataGetFileSize
+ *
+ * @param string $path
+ * @param int $size
+ */
+ public function testGetFileSize($path, $size)
+ {
+ $getFileSize = self::getMethod('getFileSize');
+
+ $monitor = new Monitor($this->request, $this->config, $this->time,
+ $this->appManager, $this->logger, $this->rootFolder,
+ $this->entropyAnalyzer, $this->mapper, $this->fileNameAnalyzer,
+ $this->fileCorruptionAnalyzer, $this->userId);
+
+ $node = $this->createMock(File::class);
+ $node->method('getSize')
+ ->willReturn($size);
+
+ $this->rootFolder->method('get')
+ ->willReturn($node);
+
+ $folder = $this->createMock(Folder::class);
+ $userRoot = $this->createMock(Folder::class);
+ $folder->method('getParent')
+ ->willReturn($userRoot);
+
+ $userRoot->method('get')
+ ->willReturn($node);
+
+ $this->rootFolder->method('getUserFolder')
+ ->willReturn($folder);
+
+ $this->assertEquals($getFileSize->invokeArgs($monitor, [$path]), $size);
+ }
+
+ public function dataGetFileSizeNotFoundException()
+ {
+ return [
+ ['path' => '/files_trashbin/test.jpg'],
+ ['path' => '/files/test.jpg'],
+ ];
+ }
+
+ /**
+ * @expectedException OCP\Files\NotFoundException
+ * @dataProvider dataGetFileSizeNotFoundException
+ *
+ * @param string $path
+ */
+ public function testGetFileSizeNotFoundException($path)
+ {
+ $getFileSize = self::getMethod('getFileSize');
+
+ $monitor = new Monitor($this->request, $this->config, $this->time,
+ $this->appManager, $this->logger, $this->rootFolder,
+ $this->entropyAnalyzer, $this->mapper, $this->fileNameAnalyzer,
+ $this->fileCorruptionAnalyzer, $this->userId);
+
+ $node = $this->createMock(Folder::class);
+
+ $this->rootFolder->method('get')
+ ->willReturn($node);
+
+ $folder = $this->createMock(Folder::class);
+ $userRoot = $this->createMock(Folder::class);
+ $folder->method('getParent')
+ ->willReturn($userRoot);
+
+ $userRoot->method('get')
+ ->willReturn($node);
+
+ $this->rootFolder->method('getUserFolder')
+ ->willReturn($folder);
+
+ $getFileSize->invokeArgs($monitor, [$path]);
+ }
+
+ public function dataIsUploadedFile()
+ {
+ return [
+ ['path' => '/files/files.ocTransferId1234', 'return' => false],
+ ['path' => '/files/files.extension', 'return' => false],
+ ['path' => '/admin/files/test/files.extension', 'return' => true],
+ ['path' => '/admin/thumbnails/test/files.extension', 'return' => true],
+ ['path' => '/admin/files_versions/test/files.extension', 'return' => true],
+ ];
+ }
+
+ /**
+ * @dataProvider dataIsUploadedFile
+ *
+ * @param string $path
+ * @param bool $return
+ */
+ public function testIsUploadedFile($path, $return)
+ {
+ $monitor = new Monitor($this->request, $this->config, $this->time,
+ $this->appManager, $this->logger, $this->rootFolder,
+ $this->entropyAnalyzer, $this->mapper, $this->fileNameAnalyzer,
+ $this->fileCorruptionAnalyzer, $this->userId);
+
+ $isUploadedFile = self::getMethod('isUploadedFile');
+ $storage = $this->createMock(IStorage::class);
+ $this->assertEquals($isUploadedFile->invokeArgs($monitor, [$storage, $path]), $return);
+ }
+
+ public function testIsCreatingSkeletonFiles()
+ {
+ $monitor = new Monitor($this->request, $this->config, $this->time,
+ $this->appManager, $this->logger, $this->rootFolder,
+ $this->entropyAnalyzer, $this->mapper, $this->fileNameAnalyzer,
+ $this->fileCorruptionAnalyzer, $this->userId);
+
+ $isCreateingSkeletonFiles = self::getMethod('isCreatingSkeletonFiles');
+ $this->assertFalse($isCreateingSkeletonFiles->invokeArgs($monitor, []));
+ }
+
+ /**
+ * Get protected method.
+ *
+ * @param string $name
+ *
+ * @return $method
+ */
+ protected static function getMethod($name)
+ {
+ $class = new \ReflectionClass(Monitor::class);
+ $method = $class->getMethod($name);
+ $method->setAccessible(true);
+
+ return $method;
+ }
+
+ /**
+ * Sets a protected property on a given object via reflection.
+ *
+ * @param $object - instance in which protected value is being modified
+ * @param $property - property on instance being modified
+ * @param $value - new value of the property being modified
+ */
+ public static function setProtectedProperty($object, $property, $value)
+ {
+ $reflection = new \ReflectionClass($object);
+ $reflection_property = $reflection->getProperty($property);
+ $reflection_property->setAccessible(true);
+ $reflection_property->setValue($object, $value);
+ }
+}
diff --git a/tests/Unit/Notification/NotifierTest.php b/tests/Unit/Notification/NotifierTest.php
new file mode 100644
index 0000000..84786d1
--- /dev/null
+++ b/tests/Unit/Notification/NotifierTest.php
@@ -0,0 +1,175 @@
+<?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\Notification;
+
+use OCA\RansomwareDetection\AppInfo\Application;
+use OCA\RansomwareDetection\Notification\Notifier;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
+use OCP\IL10N;
+use OCP\L10N\IFactory;
+use OCP\Notification\IManager;
+use OCP\Notification\INotification;
+use OCP\IConfig;
+use Test\TestCase;
+
+class NotifierTest extends TestCase
+{
+ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+ protected $config;
+
+ /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
+ protected $l;
+
+ /** @var IFactory|\PHPUnit_Framework_MockObject_MockObject */
+ protected $l10nFactory;
+
+ /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
+ protected $userManager;
+
+ /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */
+ protected $notificationManager;
+
+ /** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
+ protected $urlGenerator;
+
+ /** @var Notifier|\PHPUnit_Framework_MockObject_MockObject */
+ protected $notifier;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->l = $this->createMock(IL10N::class);
+ $this->l->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($string, $args) {
+ return vsprintf($string, $args);
+ });
+ $this->l10nFactory = $this->createMock(IFactory::class);
+ $this->l10nFactory->expects($this->any())
+ ->method('get')
+ ->willReturn($this->l);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->notificationManager = $this->createMock(IManager::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->notifier = new Notifier($this->config, $this->l10nFactory, $this->userManager, $this->notificationManager, $this->urlGenerator, 'john');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Unknown app
+ */
+ public function testPrepareWrongApp()
+ {
+ /** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */
+ $notification = $this->createMock(INotification::class);
+
+ $notification->expects($this->once())
+ ->method('getApp')
+ ->willReturn('notifications');
+ $notification->expects($this->never())
+ ->method('getSubject');
+
+ $this->notifier->prepare($notification, 'en');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Unknown subject
+ */
+ public function testPrepareWrongSubject()
+ {
+ /** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */
+ $notification = $this->createMock(INotification::class);
+
+ $notification->expects($this->once())
+ ->method('getApp')
+ ->willReturn(Application::APP_ID);
+ $notification->expects($this->once())
+ ->method('getSubject')
+ ->willReturn('wrong subject');
+
+ $this->notifier->prepare($notification, 'en');
+ }
+
+ public function dataPrepare()
+ {
+ return [
+ ['ransomware_attack_detected', ['Detected suspicious file operations.'], ['Detected a sequence of suspicious file operations.'], true],
+
+ ];
+ }
+
+ /**
+ * @dataProvider dataPrepare
+ *
+ * @param string $subject
+ * @param array $subjectParams
+ * @param array $messageParams
+ * @param bool $setMessage
+ */
+ public function testPrepare($subject, $subjectParams, $messageParams, $setMessage)
+ {
+ /** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */
+ $notification = $this->createMock(INotification::class);
+
+ $notification->expects($this->once())
+ ->method('getApp')
+ ->willReturn(Application::APP_ID);
+ $notification->expects($this->once())
+ ->method('getSubject')
+ ->willReturn($subject);
+ $notification->expects($this->once())
+ ->method('getSubjectParameters')
+ ->willReturn($subjectParams);
+ $notification->expects($this->once())
+ ->method('getMessageParameters')
+ ->willReturn($messageParams);
+ $notification->expects($this->once())
+ ->method('setParsedSubject')
+ ->with($subjectParams[0])
+ ->willReturnSelf();
+ if ($setMessage) {
+ $notification->expects($this->once())
+ ->method('setParsedMessage')
+ ->with($messageParams[0])
+ ->willReturnSelf();
+ } else {
+ $notification->expects($this->never())
+ ->method('setParsedMessage');
+ }
+ $this->urlGenerator->expects($this->once())
+ ->method('imagePath')
+ ->with(Application::APP_ID, 'app-dark.svg')
+ ->willReturn('icon-url');
+ $notification->expects($this->once())
+ ->method('setIcon')
+ ->with('icon-url')
+ ->willReturnSelf();
+ $return = $this->notifier->prepare($notification, 'en');
+
+ $this->assertEquals($notification, $return);
+ }
+}
diff --git a/tests/Unit/Service/FileOperationServiceTest.php b/tests/Unit/Service/FileOperationServiceTest.php
new file mode 100644
index 0000000..be9dca8
--- /dev/null
+++ b/tests/Unit/Service/FileOperationServiceTest.php
@@ -0,0 +1,253 @@
+<?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\Service;
+
+use OCA\RansomwareDetection\Service\FileOperationService;
+use OCA\RansomwareDetection\Db\FileOperation;
+use OCA\RansomwareDetection\Db\FileOperationMapper;
+use OCA\RansomwareDetection\Tests\Unit\Db\MapperTestUtility;
+
+class FileOperationServiceTest extends MapperTestUtility
+{
+ /** @var FileOperationService */
+ protected $service;
+
+ /** @var FileOperationMapper */
+ protected $mapper;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->mapper = new FileOperationMapper($this->db);
+ $this->service = new FileOperationService($this->mapper, 'john');
+
+ // create mock FileOperation
+ $fileOperation1 = new FileOperation();
+ $fileOperation2 = new FileOperation();
+
+ $this->fileOperations = [$fileOperation1, $fileOperation2];
+
+ $this->twoRows = [
+ ['id' => $this->fileOperations[0]->getId()],
+ ['id' => $this->fileOperations[1]->getId()],
+ ];
+ }
+
+ public function testFind()
+ {
+ $userId = 'john';
+ $id = 3;
+ $rows = [['id' => $this->fileOperations[0]->getId()]];
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `id` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$id, $userId], $rows);
+
+ $result = $this->service->find($id);
+ $this->assertEquals($this->fileOperations[0], $result);
+ }
+
+ public function testFindNotFound()
+ {
+ $userId = 'john';
+ $id = 3;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `id` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$id, $userId]);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\DoesNotExistException'
+ );
+ $this->service->find($id);
+ }
+
+ public function testFindMoreThanOneResultFound()
+ {
+ $userId = 'john';
+ $id = 3;
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `id` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$id, $userId], $rows);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\MultipleObjectsReturnedException'
+ );
+ $this->service->find($id);
+ }
+
+ public function testFindOneByFileName()
+ {
+ $userId = 'john';
+ $name = 'test';
+ $rows = [['id' => $this->fileOperations[0]->getId()]];
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `original_name` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$name, $userId], $rows);
+
+ $result = $this->service->findOneByFileName($name);
+ $this->assertEquals($this->fileOperations[0], $result);
+ }
+
+ public function testFindOneByFileNameNotFound()
+ {
+ $userId = 'john';
+ $name = 'test';
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `original_name` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$name, $userId]);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\DoesNotExistException'
+ );
+ $this->service->findOneByFileName($name);
+ }
+
+ public function testFindOneByFileNameMoreThanOneResultFound()
+ {
+ $userId = 'john';
+ $name = 'test';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` '.
+ 'WHERE `original_name` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$name, $userId], $rows);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\MultipleObjectsReturnedException'
+ );
+ $this->service->findOneByFileName($name);
+ }
+
+ public function testFindOneWithHighestId()
+ {
+ $userId = 'john';
+ $rows = [['id' => $this->fileOperations[0]->getId()]];
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?'.
+ 'ORDER BY id DESC LIMIT 1';
+
+ $this->setMapperResult($sql, [$userId], $rows);
+
+ $result = $this->service->findOneWithHighestId();
+ $this->assertEquals($this->fileOperations[0], $result);
+ }
+
+ public function testFindOneWithHighestIdNotFound()
+ {
+ $userId = 'john';
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?'.
+ 'ORDER BY id DESC LIMIT 1';
+
+ $this->setMapperResult($sql, [$userId]);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\DoesNotExistException'
+ );
+ $this->service->findOneWithHighestId();
+ }
+
+ public function testFindOneWithHighestIdMoreThanOneResultFound()
+ {
+ $userId = 'john';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?'.
+ 'ORDER BY id DESC LIMIT 1';
+
+ $this->setMapperResult($sql, [$userId], $rows);
+ $this->setExpectedException(
+ '\OCP\AppFramework\Db\MultipleObjectsReturnedException'
+ );
+ $this->service->findOneWithHighestId();
+ }
+
+ public function testFindAll()
+ {
+ $userId = 'john';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `user_id` = ?';
+
+ $this->setMapperResult($sql, [$userId], $rows);
+ $result = $this->service->findAll();
+ $this->assertEquals($this->fileOperations, $result);
+ }
+
+ public function testFindSequenceById()
+ {
+ $userId = 'john';
+ $sequence = '1';
+ $rows = $this->twoRows;
+ $sql = 'SELECT * FROM `*PREFIX*ransomware_detection_file_operation` WHERE `sequence` = ? AND `user_id` = ?';
+
+ $this->setMapperResult($sql, [$sequence, $userId], $rows);
+ $result = $this->service->findSequenceById([$sequence]);
+ $this->assertEquals($this->fileOperations, $result);
+ }
+
+ public function testDeleteById()
+ {
+ $userId = 'john';
+ $fileOperation = new FileOperation();
+ $fileOperation->setUserId($userId);
+ $fileOperation->setId(3);
+
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `id` = ? AND `user_id` = ?';
+ $arguments = [$fileOperation->getId(), $userId];
+
+ $this->setMapperResult($sql, $arguments, [], null, null, true);
+
+ $this->service->deleteById($fileOperation->getId());
+ }
+
+ public function testDeleteSequenceById()
+ {
+ $userId = 'john';
+ $fileOperation = new FileOperation();
+ $fileOperation->setId(3);
+ $fileOperation->setUserId($userId);
+ $fileOperation->setSequence(1);
+
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `sequence` = ? AND `user_id` = ?';
+ $arguments = [$fileOperation->getSequence(), $userId];
+
+ $this->setMapperResult($sql, $arguments, [], null, null, true);
+
+ $this->service->deleteSequenceById($fileOperation->getSequence());
+ }
+
+ public function testDeleteFileOperationsBefore()
+ {
+ $userId = 'john';
+ $fileOperation = new FileOperation();
+ $fileOperation->setId(3);
+ $fileOperation->setUserId($userId);
+ $fileOperation->setSequence(1);
+ $fileOperation->setTimestamp(strtotime('-1 week'));
+
+ $sql = 'DELETE FROM `*PREFIX*ransomware_detection_file_operation` WHERE `timestamp` < ?';
+ $time = time();
+ $arguments = [$time];
+
+ $this->setMapperResult($sql, $arguments, [], null, null, true);
+
+ $this->service->deleteFileOperationsBefore($time);
+ }
+}
diff --git a/tests/Unit/StorageWrapperTest.php b/tests/Unit/StorageWrapperTest.php
new file mode 100644
index 0000000..05a4996
--- /dev/null
+++ b/tests/Unit/StorageWrapperTest.php
@@ -0,0 +1,198 @@
+<?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()
+ {
+ parent::setUp();
+
+ $this->storage = $this->getMockBuilder('OCP\Files\Storage\IStorage')
+ ->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];
+
+ 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]));
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..eb8e1e5
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,19 @@
+<?php
+
+if (!defined('PHPUNIT_RUN')) {
+ define('PHPUNIT_RUN', 1);
+}
+
+require_once __DIR__.'/../../../lib/base.php';
+
+// Fix for "Autoload path not allowed: .../tests/lib/testcase.php"
+\OC::$loader->addValidRoot(OC::$SERVERROOT.'/tests');
+
+// Fix for "Autoload path not allowed: .../ransomware_detection/tests/testcase.php"
+\OC_App::loadApp('ransomware_detection');
+
+if (!class_exists('PHPUnit_Framework_TestCase')) {
+ require_once 'PHPUnit/Autoload.php';
+}
+
+OC_Hook::clear();