diff options
author | Matthias Held <ilovemilk@wusa.io> | 2018-06-18 15:14:17 +0300 |
---|---|---|
committer | Matthias Held <ilovemilk@wusa.io> | 2018-06-18 15:14:17 +0300 |
commit | 0d4208bd4934d83654fc3893867b2557546b404a (patch) | |
tree | b6db2416bb0da30e119fdf8ff2120dea7d086481 /tests | |
parent | 7a756a94ab887209f7ad7ffc6a01e2d16d01bfd4 (diff) |
Add Nextcloud application
Diffstat (limited to 'tests')
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(); |