aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Encryption
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Encryption')
-rw-r--r--tests/lib/Encryption/DecryptAllTest.php404
-rw-r--r--tests/lib/Encryption/EncryptionWrapperTest.php83
-rw-r--r--tests/lib/Encryption/Keys/StorageTest.php611
-rw-r--r--tests/lib/Encryption/ManagerTest.php274
-rw-r--r--tests/lib/Encryption/UpdateTest.php190
-rw-r--r--tests/lib/Encryption/UtilTest.php248
6 files changed, 1810 insertions, 0 deletions
diff --git a/tests/lib/Encryption/DecryptAllTest.php b/tests/lib/Encryption/DecryptAllTest.php
new file mode 100644
index 00000000000..979e12e03b3
--- /dev/null
+++ b/tests/lib/Encryption/DecryptAllTest.php
@@ -0,0 +1,404 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Encryption;
+
+use OC\Encryption\DecryptAll;
+use OC\Encryption\Exceptions\DecryptionFailedException;
+use OC\Encryption\Manager;
+use OC\Files\FileInfo;
+use OC\Files\View;
+use OCP\Files\Storage\IStorage;
+use OCP\IUserManager;
+use OCP\UserInterface;
+use Symfony\Component\Console\Formatter\OutputFormatterInterface;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Test\TestCase;
+
+/**
+ * Class DecryptAllTest
+ *
+ * @group DB
+ *
+ * @package Test\Encryption
+ */
+class DecryptAllTest extends TestCase {
+ /** @var \PHPUnit\Framework\MockObject\MockObject | IUserManager */
+ protected $userManager;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject | Manager */
+ protected $encryptionManager;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject | View */
+ protected $view;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject | \Symfony\Component\Console\Input\InputInterface */
+ protected $inputInterface;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject | \Symfony\Component\Console\Output\OutputInterface */
+ protected $outputInterface;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject|UserInterface */
+ protected $userInterface;
+
+ /** @var DecryptAll */
+ protected $instance;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->userManager = $this->getMockBuilder(IUserManager::class)
+ ->disableOriginalConstructor()->getMock();
+ $this->encryptionManager = $this->getMockBuilder('OC\Encryption\Manager')
+ ->disableOriginalConstructor()->getMock();
+ $this->view = $this->getMockBuilder(View::class)
+ ->disableOriginalConstructor()->getMock();
+ $this->inputInterface = $this->getMockBuilder(InputInterface::class)
+ ->disableOriginalConstructor()->getMock();
+ $this->outputInterface = $this->getMockBuilder(OutputInterface::class)
+ ->disableOriginalConstructor()->getMock();
+ $this->outputInterface->expects($this->any())->method('isDecorated')
+ ->willReturn(false);
+ $this->userInterface = $this->getMockBuilder(UserInterface::class)
+ ->disableOriginalConstructor()->getMock();
+
+ /* We need format method to return a string */
+ $outputFormatter = $this->createMock(OutputFormatterInterface::class);
+ $outputFormatter->method('format')->willReturn('foo');
+ $outputFormatter->method('isDecorated')->willReturn(false);
+
+ $this->outputInterface->expects($this->any())->method('getFormatter')
+ ->willReturn($outputFormatter);
+
+ $this->instance = new DecryptAll($this->encryptionManager, $this->userManager, $this->view);
+
+ $this->invokePrivate($this->instance, 'input', [$this->inputInterface]);
+ $this->invokePrivate($this->instance, 'output', [$this->outputInterface]);
+ }
+
+ public static function dataDecryptAll(): array {
+ return [
+ [true, 'user1', true],
+ [false, 'user1', true],
+ [true, '0', true],
+ [false, '0', true],
+ [true, '', false],
+ ];
+ }
+
+ /**
+ * @param bool $prepareResult
+ * @param string $user
+ * @param bool $userExistsChecked
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataDecryptAll')]
+ public function testDecryptAll($prepareResult, $user, $userExistsChecked): void {
+ if ($userExistsChecked) {
+ $this->userManager->expects($this->once())->method('userExists')->willReturn(true);
+ } else {
+ $this->userManager->expects($this->never())->method('userExists');
+ }
+ /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject | $instance */
+ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
+ ->setConstructorArgs(
+ [
+ $this->encryptionManager,
+ $this->userManager,
+ $this->view
+ ]
+ )
+ ->onlyMethods(['prepareEncryptionModules', 'decryptAllUsersFiles'])
+ ->getMock();
+
+ $instance->expects($this->once())
+ ->method('prepareEncryptionModules')
+ ->with($user)
+ ->willReturn($prepareResult);
+
+ if ($prepareResult) {
+ $instance->expects($this->once())
+ ->method('decryptAllUsersFiles')
+ ->with($user);
+ } else {
+ $instance->expects($this->never())->method('decryptAllUsersFiles');
+ }
+
+ $instance->decryptAll($this->inputInterface, $this->outputInterface, $user);
+ }
+
+ /**
+ * test decrypt all call with a user who doesn't exists
+ */
+ public function testDecryptAllWrongUser(): void {
+ $this->userManager->expects($this->once())->method('userExists')->willReturn(false);
+ $this->outputInterface->expects($this->once())->method('writeln')
+ ->with('User "user1" does not exist. Please check the username and try again');
+
+ $this->assertFalse(
+ $this->instance->decryptAll($this->inputInterface, $this->outputInterface, 'user1')
+ );
+ }
+
+ public static function dataTrueFalse(): array {
+ return [
+ [true],
+ [false],
+ ];
+ }
+
+ /**
+ * @param bool $success
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTrueFalse')]
+ public function testPrepareEncryptionModules($success): void {
+ $user = 'user1';
+
+ $dummyEncryptionModule = $this->getMockBuilder('OCP\Encryption\IEncryptionModule')
+ ->disableOriginalConstructor()->getMock();
+
+ $dummyEncryptionModule->expects($this->once())
+ ->method('prepareDecryptAll')
+ ->with($this->inputInterface, $this->outputInterface, $user)
+ ->willReturn($success);
+
+ $callback = function () use ($dummyEncryptionModule) {
+ return $dummyEncryptionModule;
+ };
+ $moduleDescription = [
+ 'id' => 'id',
+ 'displayName' => 'displayName',
+ 'callback' => $callback
+ ];
+
+ $this->encryptionManager->expects($this->once())
+ ->method('getEncryptionModules')
+ ->willReturn([$moduleDescription]);
+
+ $this->assertSame($success,
+ $this->invokePrivate($this->instance, 'prepareEncryptionModules', [$user])
+ );
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestDecryptAllUsersFiles')]
+ public function testDecryptAllUsersFiles($user): void {
+ /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject | $instance */
+ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
+ ->setConstructorArgs(
+ [
+ $this->encryptionManager,
+ $this->userManager,
+ $this->view
+ ]
+ )
+ ->onlyMethods(['decryptUsersFiles'])
+ ->getMock();
+
+ $this->invokePrivate($instance, 'input', [$this->inputInterface]);
+ $this->invokePrivate($instance, 'output', [$this->outputInterface]);
+
+ if (empty($user)) {
+ $this->userManager->expects($this->once())
+ ->method('getBackends')
+ ->willReturn([$this->userInterface]);
+ $this->userInterface->expects($this->any())
+ ->method('getUsers')
+ ->willReturn(['user1', 'user2']);
+ $calls = [
+ 'user1',
+ 'user2',
+ ];
+ $instance->expects($this->exactly(2))
+ ->method('decryptUsersFiles')
+ ->willReturnCallback(function ($user) use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, $user);
+ });
+ } else {
+ $instance->expects($this->once())
+ ->method('decryptUsersFiles')
+ ->with($user);
+ }
+
+ $this->invokePrivate($instance, 'decryptAllUsersFiles', [$user]);
+ }
+
+ public static function dataTestDecryptAllUsersFiles(): array {
+ return [
+ ['user1'],
+ ['']
+ ];
+ }
+
+ public function testDecryptUsersFiles(): void {
+ /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */
+ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
+ ->setConstructorArgs(
+ [
+ $this->encryptionManager,
+ $this->userManager,
+ $this->view
+ ]
+ )
+ ->onlyMethods(['decryptFile'])
+ ->getMock();
+
+ $storage = $this->getMockBuilder(IStorage::class)
+ ->disableOriginalConstructor()->getMock();
+
+
+ $sharedStorage = $this->getMockBuilder(IStorage::class)
+ ->disableOriginalConstructor()->getMock();
+
+ $sharedStorage->expects($this->once())
+ ->method('instanceOfStorage')
+ ->with('OCA\Files_Sharing\SharedStorage')
+ ->willReturn(true);
+
+ $this->view->expects($this->exactly(2))
+ ->method('getDirectoryContent')
+ ->willReturnMap([
+ [
+ '/user1/files', '', null,
+ [
+ new FileInfo('path', $storage, 'intPath', ['name' => 'foo', 'type' => 'dir'], null),
+ new FileInfo('path', $storage, 'intPath', ['name' => 'bar', 'type' => 'file', 'encrypted' => true], null),
+ new FileInfo('path', $sharedStorage, 'intPath', ['name' => 'shared', 'type' => 'file', 'encrypted' => true], null),
+ ],
+ ],
+ [
+ '/user1/files/foo', '', null,
+ [
+ new FileInfo('path', $storage, 'intPath', ['name' => 'subfile', 'type' => 'file', 'encrypted' => true], null)
+ ],
+ ],
+ ]);
+
+ $this->view->expects($this->any())->method('is_dir')
+ ->willReturnCallback(
+ function ($path) {
+ if ($path === '/user1/files/foo') {
+ return true;
+ }
+ return false;
+ }
+ );
+
+ $calls = [
+ '/user1/files/bar',
+ '/user1/files/foo/subfile',
+ ];
+ $instance->expects($this->exactly(2))
+ ->method('decryptFile')
+ ->willReturnCallback(function ($path) use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, $path);
+ });
+
+
+ /* We need format method to return a string */
+ $outputFormatter = $this->createMock(OutputFormatterInterface::class);
+ $outputFormatter->method('isDecorated')->willReturn(false);
+ $outputFormatter->method('format')->willReturn('foo');
+
+ $output = $this->createMock(OutputInterface::class);
+ $output->expects($this->any())
+ ->method('getFormatter')
+ ->willReturn($outputFormatter);
+ $progressBar = new ProgressBar($output);
+
+ $this->invokePrivate($instance, 'decryptUsersFiles', ['user1', $progressBar, '']);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTrueFalse')]
+ public function testDecryptFile($isEncrypted): void {
+ $path = 'test.txt';
+
+ /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */
+ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
+ ->setConstructorArgs(
+ [
+ $this->encryptionManager,
+ $this->userManager,
+ $this->view
+ ]
+ )
+ ->onlyMethods(['getTimestamp'])
+ ->getMock();
+
+ $fileInfo = $this->createMock(FileInfo::class);
+ $fileInfo->expects($this->any())->method('isEncrypted')
+ ->willReturn($isEncrypted);
+ $this->view->expects($this->any())->method('getFileInfo')
+ ->willReturn($fileInfo);
+
+ if ($isEncrypted) {
+ $instance->expects($this->any())->method('getTimestamp')->willReturn(42);
+
+ $this->view->expects($this->once())
+ ->method('copy')
+ ->with($path, $path . '.decrypted.42');
+ $this->view->expects($this->once())
+ ->method('rename')
+ ->with($path . '.decrypted.42', $path);
+ } else {
+ $instance->expects($this->never())->method('getTimestamp');
+ $this->view->expects($this->never())->method('copy');
+ $this->view->expects($this->never())->method('rename');
+ }
+ $this->assertTrue(
+ $this->invokePrivate($instance, 'decryptFile', [$path])
+ );
+ }
+
+ public function testDecryptFileFailure(): void {
+ $path = 'test.txt';
+
+ /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */
+ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll')
+ ->setConstructorArgs(
+ [
+ $this->encryptionManager,
+ $this->userManager,
+ $this->view
+ ]
+ )
+ ->onlyMethods(['getTimestamp'])
+ ->getMock();
+
+
+ $fileInfo = $this->createMock(FileInfo::class);
+ $fileInfo->expects($this->any())->method('isEncrypted')
+ ->willReturn(true);
+ $this->view->expects($this->any())->method('getFileInfo')
+ ->willReturn($fileInfo);
+
+ $instance->expects($this->any())->method('getTimestamp')->willReturn(42);
+
+ $this->view->expects($this->once())
+ ->method('copy')
+ ->with($path, $path . '.decrypted.42')
+ ->willReturnCallback(function (): void {
+ throw new DecryptionFailedException();
+ });
+
+ $this->view->expects($this->never())->method('rename');
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($path . '.decrypted.42')
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('unlink')
+ ->with($path . '.decrypted.42');
+
+ $this->assertFalse(
+ $this->invokePrivate($instance, 'decryptFile', [$path])
+ );
+ }
+}
diff --git a/tests/lib/Encryption/EncryptionWrapperTest.php b/tests/lib/Encryption/EncryptionWrapperTest.php
new file mode 100644
index 00000000000..58bf5aff005
--- /dev/null
+++ b/tests/lib/Encryption/EncryptionWrapperTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Encryption;
+
+use OC\Encryption\EncryptionWrapper;
+use OC\Encryption\Manager;
+use OC\Files\Storage\Wrapper\Encryption;
+use OC\Memcache\ArrayCache;
+use OCA\Files_Trashbin\Storage;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Storage\IDisableEncryptionStorage;
+use OCP\Files\Storage\IStorage;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class EncryptionWrapperTest extends TestCase {
+ /** @var EncryptionWrapper */
+ private $instance;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject | LoggerInterface */
+ private $logger;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject | \OC\Encryption\Manager */
+ private $manager;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject|ArrayCache */
+ private $arrayCache;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->arrayCache = $this->createMock(ArrayCache::class);
+ $this->manager = $this->createMock(Manager::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->instance = new EncryptionWrapper($this->arrayCache, $this->manager, $this->logger);
+ }
+
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('provideWrapStorage')]
+ public function testWrapStorage($expectedWrapped, $wrappedStorages): void {
+ $storage = $this->getMockBuilder(IStorage::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ foreach ($wrappedStorages as $wrapper) {
+ $storage->expects($this->any())
+ ->method('instanceOfStorage')
+ ->willReturnMap([
+ [$wrapper, true],
+ ]);
+ }
+
+ $mount = $this->getMockBuilder(IMountPoint::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $returnedStorage = $this->instance->wrapStorage('mountPoint', $storage, $mount);
+
+ $this->assertEquals(
+ $expectedWrapped,
+ $returnedStorage->instanceOfStorage(Encryption::class),
+ 'Asserted that the storage is (not) wrapped with encryption'
+ );
+ }
+
+ public static function provideWrapStorage(): array {
+ return [
+ // Wrap when not wrapped or not wrapped with storage
+ [true, []],
+ [true, [Storage::class]],
+
+ // Do not wrap shared storages
+ [false, [IDisableEncryptionStorage::class]],
+ ];
+ }
+}
diff --git a/tests/lib/Encryption/Keys/StorageTest.php b/tests/lib/Encryption/Keys/StorageTest.php
new file mode 100644
index 00000000000..333d8d8ce21
--- /dev/null
+++ b/tests/lib/Encryption/Keys/StorageTest.php
@@ -0,0 +1,611 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Encryption\Keys;
+
+use OC\Encryption\Keys\Storage;
+use OC\Encryption\Util;
+use OC\Files\View;
+use OCP\IConfig;
+use OCP\Security\ICrypto;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class StorageTest extends TestCase {
+ /** @var Storage */
+ protected $storage;
+
+ /** @var MockObject|\OC\Encryption\Util */
+ protected $util;
+
+ /** @var MockObject|View */
+ protected $view;
+
+ /** @var MockObject|IConfig */
+ protected $config;
+
+ /** @var MockObject|ICrypto */
+ protected $crypto;
+
+ private array $mkdirStack = [];
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->util = $this->getMockBuilder(Util::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(array_diff(get_class_methods(Util::class), ['getFileKeyDir']))
+ ->getMock();
+
+ $this->view = $this->getMockBuilder(View::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->crypto = $this->createMock(ICrypto::class);
+ $this->crypto->method('encrypt')
+ ->willReturnCallback(function ($data, $pass) {
+ return $data;
+ });
+ $this->crypto->method('decrypt')
+ ->willReturnCallback(function ($data, $pass) {
+ return $data;
+ });
+
+ $this->config = $this->getMockBuilder(IConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->storage = new Storage($this->view, $this->util, $this->crypto, $this->config);
+ }
+
+ public function testSetFileKey(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturn(['user1', '/files/foo.txt']);
+ $this->util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnArgument(0);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn(false);
+
+ $data = json_encode(['key' => base64_encode('key')]);
+ $this->view->expects($this->once())
+ ->method('file_put_contents')
+ ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey'),
+ $this->equalTo($data))
+ ->willReturn(strlen($data));
+
+ $this->assertTrue(
+ $this->storage->setFileKey('user1/files/foo.txt', 'fileKey', 'key', 'encModule')
+ );
+ }
+
+ public function testSetFileOld(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.0');
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturn(['user1', '/files/foo.txt']);
+ $this->util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnArgument(0);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn(false);
+ $this->crypto->expects($this->never())
+ ->method('encrypt');
+ $this->view->expects($this->once())
+ ->method('file_put_contents')
+ ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey'),
+ $this->equalTo('key'))
+ ->willReturn(strlen('key'));
+
+ $this->assertTrue(
+ $this->storage->setFileKey('user1/files/foo.txt', 'fileKey', 'key', 'encModule')
+ );
+ }
+
+ public static function dataTestGetFileKey() {
+ return [
+ ['/files/foo.txt', '/files/foo.txt', true, 'key'],
+ ['/files/foo.txt.ocTransferId2111130212.part', '/files/foo.txt', true, 'key'],
+ ['/files/foo.txt.ocTransferId2111130212.part', '/files/foo.txt', false, 'key2'],
+ ];
+ }
+
+ /**
+ *
+ * @param string $path
+ * @param string $strippedPartialName
+ * @param bool $originalKeyExists
+ * @param string $expectedKeyContent
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetFileKey')]
+ public function testGetFileKey($path, $strippedPartialName, $originalKeyExists, $expectedKeyContent): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturnMap([
+ ['user1/files/foo.txt', ['user1', '/files/foo.txt']],
+ ['user1/files/foo.txt.ocTransferId2111130212.part', ['user1', '/files/foo.txt.ocTransferId2111130212.part']],
+ ]);
+ // we need to strip away the part file extension in order to reuse a
+ // existing key if it exists, otherwise versions will break
+ $this->util->expects($this->once())
+ ->method('stripPartialFileExtension')
+ ->willReturn('user1' . $strippedPartialName);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn(false);
+
+ $this->crypto->method('decrypt')
+ ->willReturnCallback(function ($data, $pass) {
+ return $data;
+ });
+
+ if (!$originalKeyExists) {
+ $this->view->expects($this->exactly(2))
+ ->method('file_exists')
+ ->willReturnMap([
+ ['/user1/files_encryption/keys' . $strippedPartialName . '/encModule/fileKey', $originalKeyExists],
+ ['/user1/files_encryption/keys' . $path . '/encModule/fileKey', true],
+ ]);
+
+ $this->view->expects($this->once())
+ ->method('file_get_contents')
+ ->with($this->equalTo('/user1/files_encryption/keys' . $path . '/encModule/fileKey'))
+ ->willReturn(json_encode(['key' => base64_encode('key2')]));
+ } else {
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/user1/files_encryption/keys' . $strippedPartialName . '/encModule/fileKey'))
+ ->willReturn($originalKeyExists);
+
+ $this->view->expects($this->once())
+ ->method('file_get_contents')
+ ->with($this->equalTo('/user1/files_encryption/keys' . $strippedPartialName . '/encModule/fileKey'))
+ ->willReturn(json_encode(['key' => base64_encode('key')]));
+ }
+
+ $this->assertSame($expectedKeyContent,
+ $this->storage->getFileKey('user1' . $path, 'fileKey', 'encModule')
+ );
+ }
+
+ public function testSetFileKeySystemWide(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturn(['user1', '/files/foo.txt']);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn(true);
+ $this->util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnArgument(0);
+
+ $this->crypto->method('encrypt')
+ ->willReturnCallback(function ($data, $pass) {
+ return $data;
+ });
+
+ $data = json_encode(['key' => base64_encode('key')]);
+ $this->view->expects($this->once())
+ ->method('file_put_contents')
+ ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey'),
+ $this->equalTo($data))
+ ->willReturn(strlen($data));
+
+ $this->assertTrue(
+ $this->storage->setFileKey('user1/files/foo.txt', 'fileKey', 'key', 'encModule')
+ );
+ }
+
+ public function testGetFileKeySystemWide(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturn(['user1', '/files/foo.txt']);
+ $this->util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnArgument(0);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('file_get_contents')
+ ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey'))
+ ->willReturn(json_encode(['key' => base64_encode('key')]));
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey'))
+ ->willReturn(true);
+
+ $this->assertSame('key',
+ $this->storage->getFileKey('user1/files/foo.txt', 'fileKey', 'encModule')
+ );
+ }
+
+ public function testSetSystemUserKey(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+
+ $data = json_encode([
+ 'key' => base64_encode('key'),
+ 'uid' => null]
+ );
+ $this->view->expects($this->once())
+ ->method('file_put_contents')
+ ->with($this->equalTo('/files_encryption/encModule/shareKey_56884'),
+ $this->equalTo($data))
+ ->willReturn(strlen($data));
+
+ $this->assertTrue(
+ $this->storage->setSystemUserKey('shareKey_56884', 'key', 'encModule')
+ );
+ }
+
+ public function testSetUserKey(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+
+ $data = json_encode([
+ 'key' => base64_encode('key'),
+ 'uid' => 'user1']
+ );
+ $this->view->expects($this->once())
+ ->method('file_put_contents')
+ ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey'),
+ $this->equalTo($data))
+ ->willReturn(strlen($data));
+
+ $this->assertTrue(
+ $this->storage->setUserKey('user1', 'publicKey', 'key', 'encModule')
+ );
+ }
+
+ public function testGetSystemUserKey(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+
+ $data = json_encode([
+ 'key' => base64_encode('key'),
+ 'uid' => null]
+ );
+ $this->view->expects($this->once())
+ ->method('file_get_contents')
+ ->with($this->equalTo('/files_encryption/encModule/shareKey_56884'))
+ ->willReturn($data);
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/files_encryption/encModule/shareKey_56884'))
+ ->willReturn(true);
+
+ $this->assertSame('key',
+ $this->storage->getSystemUserKey('shareKey_56884', 'encModule')
+ );
+ }
+
+ public function testGetUserKey(): void {
+ $this->config->method('getSystemValueString')
+ ->with('version')
+ ->willReturn('20.0.0.2');
+
+ $data = json_encode([
+ 'key' => base64_encode('key'),
+ 'uid' => 'user1']
+ );
+ $this->view->expects($this->once())
+ ->method('file_get_contents')
+ ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey'))
+ ->willReturn($data);
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey'))
+ ->willReturn(true);
+
+ $this->assertSame('key',
+ $this->storage->getUserKey('user1', 'publicKey', 'encModule')
+ );
+ }
+
+ public function testDeleteUserKey(): void {
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey'))
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('unlink')
+ ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey'))
+ ->willReturn(true);
+
+ $this->assertTrue(
+ $this->storage->deleteUserKey('user1', 'publicKey', 'encModule')
+ );
+ }
+
+ public function testDeleteSystemUserKey(): void {
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/files_encryption/encModule/shareKey_56884'))
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('unlink')
+ ->with($this->equalTo('/files_encryption/encModule/shareKey_56884'))
+ ->willReturn(true);
+
+ $this->assertTrue(
+ $this->storage->deleteSystemUserKey('shareKey_56884', 'encModule')
+ );
+ }
+
+ public function testDeleteFileKeySystemWide(): void {
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturn(['user1', '/files/foo.txt']);
+ $this->util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnArgument(0);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey'))
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('unlink')
+ ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey'))
+ ->willReturn(true);
+
+ $this->assertTrue(
+ $this->storage->deleteFileKey('user1/files/foo.txt', 'fileKey', 'encModule')
+ );
+ }
+
+ public function testDeleteFileKey(): void {
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturn(['user1', '/files/foo.txt']);
+ $this->util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnArgument(0);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn(false);
+ $this->view->expects($this->once())
+ ->method('file_exists')
+ ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey'))
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('unlink')
+ ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey'))
+ ->willReturn(true);
+
+ $this->assertTrue(
+ $this->storage->deleteFileKey('user1/files/foo.txt', 'fileKey', 'encModule')
+ );
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataProviderCopyRename')]
+ public function testRenameKeys($source, $target, $systemWideMountSource, $systemWideMountTarget, $expectedSource, $expectedTarget): void {
+ $this->view->expects($this->any())
+ ->method('file_exists')
+ ->willReturn(true);
+ $this->view->expects($this->any())
+ ->method('is_dir')
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('rename')
+ ->with(
+ $this->equalTo($expectedSource),
+ $this->equalTo($expectedTarget))
+ ->willReturn(true);
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturnCallback([$this, 'getUidAndFilenameCallback']);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturnCallback(function ($path, $owner) use ($systemWideMountSource, $systemWideMountTarget) {
+ if (strpos($path, 'source.txt') !== false) {
+ return $systemWideMountSource;
+ }
+ return $systemWideMountTarget;
+ });
+
+ $this->storage->renameKeys($source, $target);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataProviderCopyRename')]
+ public function testCopyKeys($source, $target, $systemWideMountSource, $systemWideMountTarget, $expectedSource, $expectedTarget): void {
+ $this->view->expects($this->any())
+ ->method('file_exists')
+ ->willReturn(true);
+ $this->view->expects($this->any())
+ ->method('is_dir')
+ ->willReturn(true);
+ $this->view->expects($this->once())
+ ->method('copy')
+ ->with(
+ $this->equalTo($expectedSource),
+ $this->equalTo($expectedTarget))
+ ->willReturn(true);
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturnCallback([$this, 'getUidAndFilenameCallback']);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturnCallback(function ($path, $owner) use ($systemWideMountSource, $systemWideMountTarget) {
+ if (strpos($path, 'source.txt') !== false) {
+ return $systemWideMountSource;
+ }
+ return $systemWideMountTarget;
+ });
+
+ $this->storage->copyKeys($source, $target);
+ }
+
+ public function getUidAndFilenameCallback() {
+ $args = func_get_args();
+
+ $path = $args[0];
+ $parts = explode('/', $path);
+
+ return [$parts[1], '/' . implode('/', array_slice($parts, 2))];
+ }
+
+ public static function dataProviderCopyRename() {
+ return [
+ ['/user1/files/source.txt', '/user1/files/target.txt', false, false,
+ '/user1/files_encryption/keys/files/source.txt/', '/user1/files_encryption/keys/files/target.txt/'],
+ ['/user1/files/foo/source.txt', '/user1/files/target.txt', false, false,
+ '/user1/files_encryption/keys/files/foo/source.txt/', '/user1/files_encryption/keys/files/target.txt/'],
+ ['/user1/files/source.txt', '/user1/files/foo/target.txt', false, false,
+ '/user1/files_encryption/keys/files/source.txt/', '/user1/files_encryption/keys/files/foo/target.txt/'],
+ ['/user1/files/source.txt', '/user1/files/foo/target.txt', true, true,
+ '/files_encryption/keys/files/source.txt/', '/files_encryption/keys/files/foo/target.txt/'],
+ ['/user1/files/source.txt', '/user1/files/target.txt', false, true,
+ '/user1/files_encryption/keys/files/source.txt/', '/files_encryption/keys/files/target.txt/'],
+ ['/user1/files/source.txt', '/user1/files/target.txt', true, false,
+ '/files_encryption/keys/files/source.txt/', '/user1/files_encryption/keys/files/target.txt/'],
+
+ ['/user2/files/source.txt', '/user1/files/target.txt', false, false,
+ '/user2/files_encryption/keys/files/source.txt/', '/user1/files_encryption/keys/files/target.txt/'],
+ ['/user2/files/foo/source.txt', '/user1/files/target.txt', false, false,
+ '/user2/files_encryption/keys/files/foo/source.txt/', '/user1/files_encryption/keys/files/target.txt/'],
+ ['/user2/files/source.txt', '/user1/files/foo/target.txt', false, false,
+ '/user2/files_encryption/keys/files/source.txt/', '/user1/files_encryption/keys/files/foo/target.txt/'],
+ ['/user2/files/source.txt', '/user1/files/foo/target.txt', true, true,
+ '/files_encryption/keys/files/source.txt/', '/files_encryption/keys/files/foo/target.txt/'],
+ ['/user2/files/source.txt', '/user1/files/target.txt', false, true,
+ '/user2/files_encryption/keys/files/source.txt/', '/files_encryption/keys/files/target.txt/'],
+ ['/user2/files/source.txt', '/user1/files/target.txt', true, false,
+ '/files_encryption/keys/files/source.txt/', '/user1/files_encryption/keys/files/target.txt/'],
+ ];
+ }
+
+ /**
+ *
+ * @param string $path
+ * @param boolean $systemWideMountPoint
+ * @param string $storageRoot
+ * @param string $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetPathToKeys')]
+ public function testGetPathToKeys($path, $systemWideMountPoint, $storageRoot, $expected): void {
+ $this->invokePrivate($this->storage, 'root_dir', [$storageRoot]);
+
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturnCallback([$this, 'getUidAndFilenameCallback']);
+ $this->util->expects($this->any())
+ ->method('isSystemWideMountPoint')
+ ->willReturn($systemWideMountPoint);
+
+ $this->assertSame($expected,
+ self::invokePrivate($this->storage, 'getPathToKeys', [$path])
+ );
+ }
+
+ public static function dataTestGetPathToKeys() {
+ return [
+ ['/user1/files/source.txt', false, '', '/user1/files_encryption/keys/files/source.txt/'],
+ ['/user1/files/source.txt', true, '', '/files_encryption/keys/files/source.txt/'],
+ ['/user1/files/source.txt', false, 'storageRoot', '/storageRoot/user1/files_encryption/keys/files/source.txt/'],
+ ['/user1/files/source.txt', true, 'storageRoot', '/storageRoot/files_encryption/keys/files/source.txt/'],
+ ];
+ }
+
+ public function testKeySetPreparation(): void {
+ $this->view->expects($this->any())
+ ->method('file_exists')
+ ->willReturn(false);
+ $this->view->expects($this->any())
+ ->method('is_dir')
+ ->willReturn(false);
+ $this->view->expects($this->any())
+ ->method('mkdir')
+ ->willReturnCallback([$this, 'mkdirCallback']);
+
+ $this->mkdirStack = [
+ '/user1/files_encryption/keys/foo',
+ '/user1/files_encryption/keys',
+ '/user1/files_encryption',
+ '/user1'];
+
+ self::invokePrivate($this->storage, 'keySetPreparation', ['/user1/files_encryption/keys/foo']);
+ }
+
+ public function mkdirCallback() {
+ $args = func_get_args();
+ $expected = array_pop($this->mkdirStack);
+ $this->assertSame($expected, $args[0]);
+ }
+
+
+ /**
+ * @param bool $createBackupDir
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestBackupUserKeys')]
+ public function testBackupUserKeys($createBackupDir): void {
+ $storage = $this->getMockBuilder('OC\Encryption\Keys\Storage')
+ ->setConstructorArgs([$this->view, $this->util, $this->crypto, $this->config])
+ ->onlyMethods(['getTimestamp'])
+ ->getMock();
+
+ $storage->expects($this->any())->method('getTimestamp')->willReturn('1234567');
+
+ $this->view->expects($this->once())->method('file_exists')
+ ->with('user1/files_encryption/backup')->willReturn(!$createBackupDir);
+
+ if ($createBackupDir) {
+ $calls = [
+ 'user1/files_encryption/backup',
+ 'user1/files_encryption/backup/test.encryptionModule.1234567',
+ ];
+ $this->view->expects($this->exactly(2))->method('mkdir')
+ ->willReturnCallback(function ($path) use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, $path);
+ });
+ } else {
+ $this->view->expects($this->once())->method('mkdir')
+ ->with('user1/files_encryption/backup/test.encryptionModule.1234567');
+ }
+
+ $this->view->expects($this->once())->method('copy')
+ ->with(
+ 'user1/files_encryption/encryptionModule',
+ 'user1/files_encryption/backup/test.encryptionModule.1234567'
+ )->willReturn(true);
+
+ $this->assertTrue($storage->backupUserKeys('encryptionModule', 'test', 'user1'));
+ }
+
+ public static function dataTestBackupUserKeys() {
+ return [
+ [true], [false]
+ ];
+ }
+}
diff --git a/tests/lib/Encryption/ManagerTest.php b/tests/lib/Encryption/ManagerTest.php
new file mode 100644
index 00000000000..e9b6ddae8a0
--- /dev/null
+++ b/tests/lib/Encryption/ManagerTest.php
@@ -0,0 +1,274 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace Test\Encryption;
+
+use OC\Encryption\Exceptions\ModuleAlreadyExistsException;
+use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
+use OC\Encryption\Manager;
+use OC\Encryption\Util;
+use OC\Files\View;
+use OC\Memcache\ArrayCache;
+use OCP\Encryption\IEncryptionModule;
+use OCP\IConfig;
+use OCP\IL10N;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class ManagerTest extends TestCase {
+ /** @var Manager */
+ private $manager;
+
+ /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
+ private $config;
+
+ /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
+ private $logger;
+
+ /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
+ private $l10n;
+
+ /** @var View|\PHPUnit\Framework\MockObject\MockObject */
+ private $view;
+
+ /** @var Util|\PHPUnit\Framework\MockObject\MockObject */
+ private $util;
+
+ /** @var ArrayCache|\PHPUnit\Framework\MockObject\MockObject */
+ private $arrayCache;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->config = $this->createMock(IConfig::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->view = $this->createMock(View::class);
+ $this->util = $this->createMock(Util::class);
+ $this->arrayCache = $this->createMock(ArrayCache::class);
+ $this->manager = new Manager($this->config, $this->logger, $this->l10n, $this->view, $this->util, $this->arrayCache);
+ }
+
+ public function testManagerIsDisabled(): void {
+ $this->assertFalse($this->manager->isEnabled());
+ }
+
+ public function testManagerIsDisabledIfEnabledButNoModules(): void {
+ $this->config->expects($this->any())->method('getAppValue')->willReturn(true);
+ $this->assertFalse($this->manager->isEnabled());
+ }
+
+ public function testManagerIsDisabledIfDisabledButModules(): void {
+ $this->config->expects($this->any())->method('getAppValue')->willReturn(false);
+ $em = $this->createMock(IEncryptionModule::class);
+ $em->expects($this->any())->method('getId')->willReturn('id');
+ $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
+ $this->manager->registerEncryptionModule('id', 'TestDummyModule0', function () use ($em) {
+ return $em;
+ });
+ $this->assertFalse($this->manager->isEnabled());
+ }
+
+ public function testManagerIsEnabled(): void {
+ $this->config->expects($this->any())->method('getSystemValueBool')->willReturn(true);
+ $this->config->expects($this->any())->method('getAppValue')->willReturn('yes');
+ $this->assertTrue($this->manager->isEnabled());
+ }
+
+ public function testModuleRegistration() {
+ $this->config->expects($this->any())->method('getAppValue')->willReturn('yes');
+
+ $this->addNewEncryptionModule($this->manager, 0);
+ $this->assertCount(1, $this->manager->getEncryptionModules());
+
+ return $this->manager;
+ }
+
+ /**
+ * @depends testModuleRegistration
+ */
+ public function testModuleReRegistration($manager): void {
+ $this->expectException(ModuleAlreadyExistsException::class);
+ $this->expectExceptionMessage('Id "ID0" already used by encryption module "TestDummyModule0"');
+
+ $this->addNewEncryptionModule($manager, 0);
+ }
+
+ public function testModuleUnRegistration(): void {
+ $this->config->expects($this->any())->method('getAppValue')->willReturn(true);
+ $this->addNewEncryptionModule($this->manager, 0);
+ $this->assertCount(1, $this->manager->getEncryptionModules());
+
+ $this->manager->unregisterEncryptionModule('ID0');
+ $this->assertEmpty($this->manager->getEncryptionModules());
+ }
+
+
+ public function testGetEncryptionModuleUnknown(): void {
+ $this->expectException(ModuleDoesNotExistsException::class);
+ $this->expectExceptionMessage('Module with ID: unknown does not exist.');
+
+ $this->config->expects($this->any())->method('getAppValue')->willReturn(true);
+ $this->addNewEncryptionModule($this->manager, 0);
+ $this->assertCount(1, $this->manager->getEncryptionModules());
+ $this->manager->getEncryptionModule('unknown');
+ }
+
+ public function testGetEncryptionModuleEmpty(): void {
+ global $defaultId;
+ $defaultId = null;
+
+ $this->config->expects($this->any())
+ ->method('getAppValue')
+ ->with('core', 'default_encryption_module')
+ ->willReturnCallback(function () {
+ global $defaultId;
+ return $defaultId;
+ });
+
+ $this->addNewEncryptionModule($this->manager, 0);
+ $this->assertCount(1, $this->manager->getEncryptionModules());
+ $this->addNewEncryptionModule($this->manager, 1);
+ $this->assertCount(2, $this->manager->getEncryptionModules());
+
+ // Should return the default module
+ $defaultId = 'ID0';
+ $this->assertEquals('ID0', $this->manager->getEncryptionModule()->getId());
+ $defaultId = 'ID1';
+ $this->assertEquals('ID1', $this->manager->getEncryptionModule()->getId());
+ }
+
+ public function testGetEncryptionModule(): void {
+ global $defaultId;
+ $defaultId = null;
+
+ $this->config->expects($this->any())
+ ->method('getAppValue')
+ ->with('core', 'default_encryption_module')
+ ->willReturnCallback(function () {
+ global $defaultId;
+ return $defaultId;
+ });
+
+ $this->addNewEncryptionModule($this->manager, 0);
+ $defaultId = 'ID0';
+ $this->assertCount(1, $this->manager->getEncryptionModules());
+
+ $en0 = $this->manager->getEncryptionModule('ID0');
+ $this->assertEquals('ID0', $en0->getId());
+
+ $en0 = self::invokePrivate($this->manager, 'getDefaultEncryptionModule');
+ $this->assertEquals('ID0', $en0->getId());
+
+ $this->assertEquals('ID0', $this->manager->getDefaultEncryptionModuleId());
+ }
+
+ public function testSetDefaultEncryptionModule(): void {
+ global $defaultId;
+ $defaultId = null;
+
+ $this->config->expects($this->any())
+ ->method('getAppValue')
+ ->with('core', 'default_encryption_module')
+ ->willReturnCallback(function () {
+ global $defaultId;
+ return $defaultId;
+ });
+
+ $this->addNewEncryptionModule($this->manager, 0);
+ $this->assertCount(1, $this->manager->getEncryptionModules());
+ $this->addNewEncryptionModule($this->manager, 1);
+ $this->assertCount(2, $this->manager->getEncryptionModules());
+
+ // Default module is the first we set
+ $defaultId = 'ID0';
+ $this->assertEquals('ID0', $this->manager->getDefaultEncryptionModuleId());
+
+ // Set to an existing module
+ $this->config->expects($this->once())
+ ->method('setAppValue')
+ ->with('core', 'default_encryption_module', 'ID1');
+ $this->assertTrue($this->manager->setDefaultEncryptionModule('ID1'));
+ $defaultId = 'ID1';
+ $this->assertEquals('ID1', $this->manager->getDefaultEncryptionModuleId());
+
+ // Set to an unexisting module
+ $this->assertFalse($this->manager->setDefaultEncryptionModule('ID2'));
+ $this->assertEquals('ID1', $this->manager->getDefaultEncryptionModuleId());
+ }
+
+ // /**
+ // * @expectedException \OC\Encryption\Exceptions\ModuleAlreadyExistsException
+ // * @expectedExceptionMessage Id "0" already used by encryption module "TestDummyModule0"
+ // */
+ // public function testModuleRegistration() {
+ // $config = $this->createMock(IConfig::class);
+ // $config->expects($this->any())->method('getSystemValueBool')->willReturn(true);
+ // $em = $this->createMock(IEncryptionModule::class);
+ // $em->expects($this->any())->method('getId')->willReturn(0);
+ // $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
+ // $m = new Manager($config);
+ // $m->registerEncryptionModule($em);
+ // $this->assertTrue($m->isEnabled());
+ // $m->registerEncryptionModule($em);
+ // }
+ //
+ // public function testModuleUnRegistration() {
+ // $config = $this->createMock(IConfig::class);
+ // $config->expects($this->any())->method('getSystemValueBool')->willReturn(true);
+ // $em = $this->createMock(IEncryptionModule::class);
+ // $em->expects($this->any())->method('getId')->willReturn(0);
+ // $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
+ // $m = new Manager($config);
+ // $m->registerEncryptionModule($em);
+ // $this->assertTrue($m->isEnabled());
+ // $m->unregisterEncryptionModule($em);
+ // $this->assertFalse($m->isEnabled());
+ // }
+ //
+ // /**
+ // * @expectedException \OC\Encryption\Exceptions\ModuleDoesNotExistsException
+ // * @expectedExceptionMessage Module with ID: unknown does not exist.
+ // */
+ // public function testGetEncryptionModuleUnknown() {
+ // $config = $this->createMock(IConfig::class);
+ // $config->expects($this->any())->method('getSystemValueBool')->willReturn(true);
+ // $em = $this->createMock(IEncryptionModule::class);
+ // $em->expects($this->any())->method('getId')->willReturn(0);
+ // $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
+ // $m = new Manager($config);
+ // $m->registerEncryptionModule($em);
+ // $this->assertTrue($m->isEnabled());
+ // $m->getEncryptionModule('unknown');
+ // }
+ //
+ // public function testGetEncryptionModule() {
+ // $config = $this->createMock(IConfig::class);
+ // $config->expects($this->any())->method('getSystemValueBool')->willReturn(true);
+ // $em = $this->createMock(IEncryptionModule::class);
+ // $em->expects($this->any())->method('getId')->willReturn(0);
+ // $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
+ // $m = new Manager($config);
+ // $m->registerEncryptionModule($em);
+ // $this->assertTrue($m->isEnabled());
+ // $en0 = $m->getEncryptionModule(0);
+ // $this->assertEquals(0, $en0->getId());
+ // }
+
+ protected function addNewEncryptionModule(Manager $manager, $id) {
+ $encryptionModule = $this->createMock(IEncryptionModule::class);
+ $encryptionModule->expects($this->any())
+ ->method('getId')
+ ->willReturn('ID' . $id);
+ $encryptionModule->expects($this->any())
+ ->method('getDisplayName')
+ ->willReturn('TestDummyModule' . $id);
+ /** @var IEncryptionModule $encryptionModule */
+ $manager->registerEncryptionModule('ID' . $id, 'TestDummyModule' . $id, function () use ($encryptionModule) {
+ return $encryptionModule;
+ });
+ }
+}
diff --git a/tests/lib/Encryption/UpdateTest.php b/tests/lib/Encryption/UpdateTest.php
new file mode 100644
index 00000000000..04ca224c0a1
--- /dev/null
+++ b/tests/lib/Encryption/UpdateTest.php
@@ -0,0 +1,190 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Encryption;
+
+use OC\Encryption\File;
+use OC\Encryption\Update;
+use OC\Encryption\Util;
+use OC\Files\View;
+use OCP\Encryption\IEncryptionModule;
+use OCP\Files\File as OCPFile;
+use OCP\Files\Folder;
+use OCP\IUser;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class UpdateTest extends TestCase {
+ private string $uid;
+ private View&MockObject $view;
+ private Util&MockObject $util;
+ private \OC\Encryption\Manager&MockObject $encryptionManager;
+ private IEncryptionModule&MockObject $encryptionModule;
+ private File&MockObject $fileHelper;
+ private LoggerInterface&MockObject $logger;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->view = $this->createMock(View::class);
+ $this->util = $this->createMock(Util::class);
+ $this->encryptionManager = $this->createMock(\OC\Encryption\Manager::class);
+ $this->fileHelper = $this->createMock(File::class);
+ $this->encryptionModule = $this->createMock(IEncryptionModule::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->uid = 'testUser1';
+ }
+
+ private function getUserMock(string $uid): IUser&MockObject {
+ $user = $this->createMock(IUser::class);
+ $user->expects(self::any())
+ ->method('getUID')
+ ->willReturn($uid);
+ return $user;
+ }
+
+ private function getFileMock(string $path, string $owner): OCPFile&MockObject {
+ $node = $this->createMock(OCPFile::class);
+ $node->expects(self::atLeastOnce())
+ ->method('getPath')
+ ->willReturn($path);
+ $node->expects(self::any())
+ ->method('getOwner')
+ ->willReturn($this->getUserMock($owner));
+
+ return $node;
+ }
+
+ private function getFolderMock(string $path, string $owner): Folder&MockObject {
+ $node = $this->createMock(Folder::class);
+ $node->expects(self::atLeastOnce())
+ ->method('getPath')
+ ->willReturn($path);
+ $node->expects(self::any())
+ ->method('getOwner')
+ ->willReturn($this->getUserMock($owner));
+
+ return $node;
+ }
+
+ /**
+ *
+ * @param string $path
+ * @param boolean $isDir
+ * @param array $allFiles
+ * @param integer $numberOfFiles
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestUpdate')]
+ public function testUpdate($path, $isDir, $allFiles, $numberOfFiles): void {
+ $updateMock = $this->getUpdateMock(['getOwnerPath']);
+ $updateMock->expects($this->once())->method('getOwnerPath')
+ ->willReturnCallback(fn (OCPFile|Folder $node) => '/user/' . $node->getPath());
+
+ $this->encryptionManager->expects($this->once())
+ ->method('getEncryptionModule')
+ ->willReturn($this->encryptionModule);
+
+ if ($isDir) {
+ $this->util->expects($this->once())
+ ->method('getAllFiles')
+ ->willReturn($allFiles);
+ $node = $this->getFolderMock($path, 'user');
+ } else {
+ $node = $this->getFileMock($path, 'user');
+ }
+
+ $this->fileHelper->expects($this->exactly($numberOfFiles))
+ ->method('getAccessList')
+ ->willReturn(['users' => [], 'public' => false]);
+
+ $this->encryptionModule->expects($this->exactly($numberOfFiles))
+ ->method('update')
+ ->willReturn(true);
+
+ $updateMock->update($node);
+ }
+
+ public static function dataTestUpdate(): array {
+ return [
+ ['/user/files/foo', true, ['/user/files/foo/file1.txt', '/user/files/foo/file1.txt'], 2],
+ ['/user/files/test.txt', false, [], 1],
+ ];
+ }
+
+ /**
+ *
+ * @param string $source
+ * @param string $target
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestPostRename')]
+ public function testPostRename($source, $target): void {
+ $updateMock = $this->getUpdateMock(['update','getOwnerPath']);
+
+ $sourceNode = $this->getFileMock($source, 'user');
+ $targetNode = $this->getFileMock($target, 'user');
+
+ if (dirname($source) === dirname($target)) {
+ $updateMock->expects($this->never())->method('getOwnerPath');
+ $updateMock->expects($this->never())->method('update');
+ } else {
+ $updateMock->expects($this->once())->method('update')
+ ->willReturnCallback(fn (OCPFile|Folder $node) => $this->assertSame(
+ $target,
+ $node->getPath(),
+ 'update needs to be executed for the target destination'
+ ));
+ }
+
+ $updateMock->postRename($sourceNode, $targetNode);
+ }
+
+ public static function dataTestPostRename(): array {
+ return [
+ ['/test.txt', '/testNew.txt'],
+ ['/folder/test.txt', '/testNew.txt'],
+ ['/test.txt', '/folder/testNew.txt'],
+ ];
+ }
+
+ public function testPostRestore(): void {
+ $updateMock = $this->getUpdateMock(['update']);
+
+ $updateMock->expects($this->once())->method('update')
+ ->willReturnCallback(fn (OCPFile|Folder $node) => $this->assertSame(
+ '/folder/test.txt',
+ $node->getPath(),
+ 'update needs to be executed for the target destination'
+ ));
+
+ $updateMock->postRestore($this->getFileMock('/folder/test.txt', 'user'));
+ }
+
+ /**
+ * create mock of the update method
+ *
+ * @param array $methods methods which should be set
+ */
+ protected function getUpdateMock(array $methods): Update&MockObject {
+ return $this->getMockBuilder(Update::class)
+ ->setConstructorArgs(
+ [
+ $this->util,
+ $this->encryptionManager,
+ $this->fileHelper,
+ $this->logger,
+ $this->uid
+ ]
+ )
+ ->onlyMethods($methods)
+ ->getMock();
+ }
+}
diff --git a/tests/lib/Encryption/UtilTest.php b/tests/lib/Encryption/UtilTest.php
new file mode 100644
index 00000000000..d1fefee872a
--- /dev/null
+++ b/tests/lib/Encryption/UtilTest.php
@@ -0,0 +1,248 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace Test\Encryption;
+
+use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException;
+use OC\Encryption\Util;
+use OC\Files\View;
+use OCP\Encryption\IEncryptionModule;
+use OCP\IConfig;
+use OCP\IGroupManager;
+use OCP\IUserManager;
+use Test\TestCase;
+
+class UtilTest extends TestCase {
+ /**
+ * block size will always be 8192 for a PHP stream
+ *
+ * @see https://bugs.php.net/bug.php?id=21641
+ */
+ protected static int $headerSize = 8192;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject */
+ protected $view;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject|IUserManager */
+ protected $userManager;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject|IGroupManager */
+ protected $groupManager;
+
+ /** @var \PHPUnit\Framework\MockObject\MockObject|IConfig */
+ private $config;
+ private Util $util;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->view = $this->getMockBuilder(View::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->groupManager = $this->createMock(IGroupManager::class);
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->util = new Util(
+ $this->view,
+ $this->userManager,
+ $this->groupManager,
+ $this->config
+ );
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('providesHeadersForEncryptionModule')]
+ public function testGetEncryptionModuleId($expected, $header): void {
+ $id = $this->util->getEncryptionModuleId($header);
+ $this->assertEquals($expected, $id);
+ }
+
+ public static function providesHeadersForEncryptionModule(): array {
+ return [
+ ['', []],
+ ['', ['1']],
+ [2, ['oc_encryption_module' => 2]],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('providesHeaders')]
+ public function testCreateHeader($expected, $header, $moduleId): void {
+ $em = $this->createMock(IEncryptionModule::class);
+ $em->expects($this->any())->method('getId')->willReturn($moduleId);
+
+ $result = $this->util->createHeader($header, $em);
+ $this->assertEquals($expected, $result);
+ }
+
+ public static function providesHeaders(): array {
+ return [
+ [str_pad('HBEGIN:oc_encryption_module:0:HEND', self::$headerSize, '-', STR_PAD_RIGHT)
+ , [], '0'],
+ [str_pad('HBEGIN:oc_encryption_module:0:custom_header:foo:HEND', self::$headerSize, '-', STR_PAD_RIGHT)
+ , ['custom_header' => 'foo'], '0'],
+ ];
+ }
+
+
+ public function testCreateHeaderFailed(): void {
+ $this->expectException(EncryptionHeaderKeyExistsException::class);
+
+
+ $header = ['header1' => 1, 'header2' => 2, 'oc_encryption_module' => 'foo'];
+
+ $em = $this->createMock(IEncryptionModule::class);
+ $em->expects($this->any())->method('getId')->willReturn('moduleId');
+
+ $this->util->createHeader($header, $em);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('providePathsForTestIsExcluded')]
+ public function testIsExcluded($path, $keyStorageRoot, $expected): void {
+ $this->config->expects($this->once())
+ ->method('getAppValue')
+ ->with('core', 'encryption_key_storage_root', '')
+ ->willReturn($keyStorageRoot);
+ $this->userManager
+ ->expects($this->any())
+ ->method('userExists')
+ ->willReturnCallback([$this, 'isExcludedCallback']);
+
+ $this->assertSame($expected,
+ $this->util->isExcluded($path)
+ );
+ }
+
+ public static function providePathsForTestIsExcluded(): array {
+ return [
+ ['/files_encryption', '', true],
+ ['files_encryption/foo.txt', '', true],
+ ['test/foo.txt', '', false],
+ ['/user1/files_encryption/foo.txt', '', true],
+ ['/user1/files/foo.txt', '', false],
+ ['/keyStorage/user1/files/foo.txt', 'keyStorage', true],
+ ['/keyStorage/files_encryption', '/keyStorage', true],
+ ['keyStorage/user1/files_encryption', '/keyStorage/', true],
+
+ ];
+ }
+
+ public function isExcludedCallback() {
+ $args = func_get_args();
+ if ($args[0] === 'user1') {
+ return true;
+ }
+
+ return false;
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestIsFile')]
+ public function testIsFile($path, $expected): void {
+ $this->assertSame($expected,
+ $this->util->isFile($path)
+ );
+ }
+
+ public static function dataTestIsFile(): array {
+ return [
+ ['/user/files/test.txt', true],
+ ['/user/files', true],
+ ['/user/files_versions/test.txt', false],
+ ['/user/foo/files/test.txt', false],
+ ['/files/foo/files/test.txt', false],
+ ['/user', false],
+ ['/user/test.txt', false],
+ ];
+ }
+
+ /**
+ *
+ * @param string $path
+ * @param string $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestStripPartialFileExtension')]
+ public function testStripPartialFileExtension($path, $expected): void {
+ $this->assertSame($expected,
+ $this->util->stripPartialFileExtension($path));
+ }
+
+ public static function dataTestStripPartialFileExtension(): array {
+ return [
+ ['/foo/test.txt', '/foo/test.txt'],
+ ['/foo/test.txt.part', '/foo/test.txt'],
+ ['/foo/test.txt.ocTransferId7567846853.part', '/foo/test.txt'],
+ ['/foo/test.txt.ocTransferId7567.part', '/foo/test.txt'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestParseRawHeader')]
+ public function testParseRawHeader($rawHeader, $expected): void {
+ $result = $this->util->parseRawHeader($rawHeader);
+ $this->assertSameSize($expected, $result);
+ foreach ($result as $key => $value) {
+ $this->assertArrayHasKey($key, $expected);
+ $this->assertSame($expected[$key], $value);
+ }
+ }
+
+ public static function dataTestParseRawHeader(): array {
+ return [
+ [str_pad('HBEGIN:oc_encryption_module:0:HEND', self::$headerSize, '-', STR_PAD_RIGHT)
+ , [Util::HEADER_ENCRYPTION_MODULE_KEY => '0']],
+ [str_pad('HBEGIN:oc_encryption_module:0:custom_header:foo:HEND', self::$headerSize, '-', STR_PAD_RIGHT)
+ , ['custom_header' => 'foo', Util::HEADER_ENCRYPTION_MODULE_KEY => '0']],
+ [str_pad('HelloWorld', self::$headerSize, '-', STR_PAD_RIGHT), []],
+ ['', []],
+ [str_pad('HBEGIN:oc_encryption_module:0', self::$headerSize, '-', STR_PAD_RIGHT)
+ , []],
+ [str_pad('oc_encryption_module:0:HEND', self::$headerSize, '-', STR_PAD_RIGHT)
+ , []],
+ ];
+ }
+
+ /**
+ *
+ * @param bool $isSystemWideMountPoint
+ * @param string $storageRoot
+ * @param string $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetFileKeyDir')]
+ public function testGetFileKeyDir($isSystemWideMountPoint, $storageRoot, $expected): void {
+ $path = '/user1/files/foo/bar.txt';
+ $owner = 'user1';
+ $relativePath = '/foo/bar.txt';
+
+ $util = $this->getMockBuilder(Util::class)
+ ->onlyMethods(['isSystemWideMountPoint', 'getUidAndFilename', 'getKeyStorageRoot'])
+ ->setConstructorArgs([
+ $this->view,
+ $this->userManager,
+ $this->groupManager,
+ $this->config
+ ])
+ ->getMock();
+
+ $util->expects($this->once())->method('getKeyStorageRoot')
+ ->willReturn($storageRoot);
+ $util->expects($this->once())->method('isSystemWideMountPoint')
+ ->willReturn($isSystemWideMountPoint);
+ $util->expects($this->once())->method('getUidAndFilename')
+ ->with($path)->willReturn([$owner, $relativePath]);
+
+ $this->assertSame($expected,
+ $util->getFileKeyDir('OC_DEFAULT_MODULE', $path)
+ );
+ }
+
+ public static function dataTestGetFileKeyDir(): array {
+ return [
+ [false, '', '/user1/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'],
+ [true, '', '/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'],
+ [false, 'newStorageRoot', '/newStorageRoot/user1/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'],
+ [true, 'newStorageRoot', '/newStorageRoot/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'],
+ ];
+ }
+}