diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php index 60decd4c8461e..53cab1df50830 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php @@ -5,6 +5,7 @@ * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ + namespace OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; use OCP\AppFramework\Http; diff --git a/apps/encryption/lib/Command/DropLegacyFileKey.php b/apps/encryption/lib/Command/DropLegacyFileKey.php index c9f6e9440e203..17349d66b91d0 100644 --- a/apps/encryption/lib/Command/DropLegacyFileKey.php +++ b/apps/encryption/lib/Command/DropLegacyFileKey.php @@ -85,7 +85,7 @@ private function scanFolder(OutputInterface $output, string $folder): bool { $output->writeln('' . $path . ' does not have a proper header'); } else { try { - $legacyFileKey = $this->keyManager->getFileKey($path, null, true); + $legacyFileKey = $this->keyManager->getFileKey($path, true); if ($legacyFileKey === '') { $output->writeln('Got an empty legacy filekey for ' . $path . ', continuing', OutputInterface::VERBOSITY_VERBOSE); continue; diff --git a/apps/encryption/lib/Crypto/Crypt.php b/apps/encryption/lib/Crypto/Crypt.php index 92d6ed6a44393..30da13dd3bff2 100644 --- a/apps/encryption/lib/Crypto/Crypt.php +++ b/apps/encryption/lib/Crypto/Crypt.php @@ -342,9 +342,8 @@ public function encryptPrivateKey($privateKey, $password, $uid = '') { * @param string $privateKey * @param string $password * @param string $uid for regular users, empty for system keys - * @return false|string */ - public function decryptPrivateKey($privateKey, $password = '', $uid = '') { + public function decryptPrivateKey($privateKey, $password = '', $uid = '') : string|false { $header = $this->parseHeader($privateKey); if (isset($header['cipher'])) { diff --git a/apps/encryption/lib/Crypto/DecryptAll.php b/apps/encryption/lib/Crypto/DecryptAll.php index 80c187571b7db..ce4f8e1fe6093 100644 --- a/apps/encryption/lib/Crypto/DecryptAll.php +++ b/apps/encryption/lib/Crypto/DecryptAll.php @@ -1,10 +1,13 @@ util->isMasterKeyEnabled()) { @@ -71,7 +69,7 @@ public function prepare(InputInterface $input, OutputInterface $output, $user) { $password = $this->keyManager->getMasterKeyPassword(); } else { $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); - if (!empty($user)) { + if ($user !== null && $user !== '') { $output->writeln('You can only decrypt the users files if you know'); $output->writeln('the users password or if he activated the recovery key.'); $output->writeln(''); @@ -120,7 +118,7 @@ public function prepare(InputInterface $input, OutputInterface $output, $user) { * @return bool|string * @throws \OCA\Encryption\Exceptions\PrivateKeyMissingException */ - protected function getPrivateKey($user, $password) { + protected function getPrivateKey(string $user, string $password): string|false { $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); $masterKeyId = $this->keyManager->getMasterKeyId(); if ($user === $recoveryKeyId) { @@ -137,7 +135,7 @@ protected function getPrivateKey($user, $password) { return $privateKey; } - protected function updateSession($user, $privateKey) { + protected function updateSession(string $user, string $privateKey): void { $this->session->prepareDecryptAll($user, $privateKey); } } diff --git a/apps/encryption/lib/Crypto/EncryptAll.php b/apps/encryption/lib/Crypto/EncryptAll.php index 61c0a2af393e6..4adf6da94fcdb 100644 --- a/apps/encryption/lib/Crypto/EncryptAll.php +++ b/apps/encryption/lib/Crypto/EncryptAll.php @@ -1,10 +1,13 @@ input = $input; $this->output = $output; @@ -155,7 +155,7 @@ public function encryptAll(InputInterface $input, OutputInterface $output) { /** * create key-pair for every user */ - protected function createKeyPairs() { + protected function createKeyPairs(): void { $this->output->writeln("\n"); $progress = new ProgressBar($this->output); $progress->setFormat(" %message% \n [%bar%]"); @@ -190,7 +190,7 @@ protected function createKeyPairs() { /** * iterate over all user and encrypt their files */ - protected function encryptAllUsersFiles() { + protected function encryptAllUsersFiles(): void { $this->output->writeln("\n"); $progress = new ProgressBar($this->output); $progress->setFormat(" %message% \n [%bar%]"); @@ -212,10 +212,8 @@ protected function encryptAllUsersFiles() { /** * encrypt all user files with the master key - * - * @param ProgressBar $progress */ - protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress) { + protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress): void { $userNo = 1; foreach ($this->userManager->getBackends() as $backend) { $limit = 500; @@ -234,12 +232,8 @@ protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress) { /** * encrypt files from the given user - * - * @param string $uid - * @param ProgressBar $progress - * @param string $userCount */ - protected function encryptUsersFiles($uid, ProgressBar $progress, $userCount) { + protected function encryptUsersFiles(string $uid, ProgressBar $progress, string $userCount): void { $this->setupUserFS($uid); $directories = []; $directories[] = '/' . $uid . '/files'; @@ -312,7 +306,7 @@ protected function encryptFile(FileInfo $fileInfo, string $path): bool { /** * output one-time encryption passwords */ - protected function outputPasswords() { + protected function outputPasswords(): void { $table = new Table($this->output); $table->setHeaders(['Username', 'Private key password']); @@ -353,10 +347,8 @@ protected function outputPasswords() { /** * write one-time encryption passwords to a csv file - * - * @param array $passwords */ - protected function writePasswordsToFile(array $passwords) { + protected function writePasswordsToFile(array $passwords): void { $fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w'); foreach ($passwords as $pwd) { fputcsv($fp, $pwd); @@ -374,10 +366,8 @@ protected function writePasswordsToFile(array $passwords) { /** * setup user file system - * - * @param string $uid */ - protected function setupUserFS($uid) { + protected function setupUserFS(string $uid): void { \OC_Util::tearDownFS(); \OC_Util::setupFS($uid); } @@ -385,10 +375,9 @@ protected function setupUserFS($uid) { /** * generate one time password for the user and store it in a array * - * @param string $uid * @return string password */ - protected function generateOneTimePassword($uid) { + protected function generateOneTimePassword(string $uid): string { $password = $this->secureRandom->generate(16, ISecureRandom::CHAR_HUMAN_READABLE); $this->userPasswords[$uid] = $password; return $password; @@ -397,7 +386,7 @@ protected function generateOneTimePassword($uid) { /** * send encryption key passwords to the users by mail */ - protected function sendPasswordsByMail() { + protected function sendPasswordsByMail(): void { $noMail = []; $this->output->writeln(''); diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php index 04fee2fd92ae7..c4c598f02295c 100644 --- a/apps/encryption/lib/Crypto/Encryption.php +++ b/apps/encryption/lib/Crypto/Encryption.php @@ -437,7 +437,7 @@ public function getUnencryptedBlockSize($signed = false) { * @throws DecryptionFailedException */ public function isReadable($path, $uid) { - $fileKey = $this->keyManager->getFileKey($path, $uid, null); + $fileKey = $this->keyManager->getFileKey($path, null); if (empty($fileKey)) { $owner = $this->util->getOwner($path); if ($owner !== $uid) { diff --git a/apps/encryption/lib/KeyManager.php b/apps/encryption/lib/KeyManager.php index 7b55a04f06921..a50d72215d3b0 100644 --- a/apps/encryption/lib/KeyManager.php +++ b/apps/encryption/lib/KeyManager.php @@ -136,7 +136,11 @@ public function validateMasterKey() { if (!$this->session->isPrivateKeySet()) { $masterKey = $this->getSystemPrivateKey($this->masterKeyId); $decryptedMasterKey = $this->crypt->decryptPrivateKey($masterKey, $this->getMasterKeyPassword(), $this->masterKeyId); - $this->session->setPrivateKey($decryptedMasterKey); + if ($decryptedMasterKey === false) { + $this->logger->error('A public master key is available but decrypting it failed. This should never happen.'); + } else { + $this->session->setPrivateKey($decryptedMasterKey); + } } // after the encryption key is available we are ready to go diff --git a/apps/encryption/lib/Recovery.php b/apps/encryption/lib/Recovery.php index 3990067f19b23..384bda689ba60 100644 --- a/apps/encryption/lib/Recovery.php +++ b/apps/encryption/lib/Recovery.php @@ -90,12 +90,8 @@ public function enableAdminRecovery($password) { /** * change recovery key id - * - * @param string $newPassword - * @param string $oldPassword - * @return bool */ - public function changeRecoveryKeyPassword($newPassword, $oldPassword) { + public function changeRecoveryKeyPassword(string $newPassword, string $oldPassword): bool { $recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId()); $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword); if ($decryptedRecoveryKey === false) { @@ -103,7 +99,7 @@ public function changeRecoveryKeyPassword($newPassword, $oldPassword) { } $encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword); $header = $this->crypt->generateHeader(); - if ($encryptedRecoveryKey) { + if ($encryptedRecoveryKey !== false) { $this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $header . $encryptedRecoveryKey); return true; } @@ -186,7 +182,7 @@ private function addRecoveryKeys(string $path): void { if ($item['type'] === 'dir') { $this->addRecoveryKeys($filePath . '/'); } else { - $fileKey = $this->keyManager->getFileKey($filePath, $this->user->getUID(), null); + $fileKey = $this->keyManager->getFileKey($filePath, null); if (!empty($fileKey)) { $accessList = $this->file->getAccessList($filePath); $publicKeys = []; diff --git a/apps/encryption/lib/Session.php b/apps/encryption/lib/Session.php index e16396e8fafab..8d9020f733d07 100644 --- a/apps/encryption/lib/Session.php +++ b/apps/encryption/lib/Session.php @@ -1,10 +1,13 @@ session->set('encryptionInitialized', $status); } @@ -40,7 +43,7 @@ public function setStatus($status) { * * @return string init status INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INITIALIZED */ - public function getStatus() { + public function getStatus(): string { $status = $this->session->get('encryptionInitialized'); if (is_null($status)) { $status = self::NOT_INITIALIZED; @@ -51,10 +54,8 @@ public function getStatus() { /** * check if encryption was initialized successfully - * - * @return bool */ - public function isReady() { + public function isReady(): bool { $status = $this->getStatus(); return $status === self::INIT_SUCCESSFUL; } @@ -65,7 +66,7 @@ public function isReady() { * @return string $privateKey The user's plaintext private key * @throws Exceptions\PrivateKeyMissingException */ - public function getPrivateKey() { + public function getPrivateKey(): string { $key = $this->session->get('privateKey'); if (is_null($key)) { throw new Exceptions\PrivateKeyMissingException('please try to log-out and log-in again', 0); @@ -75,10 +76,8 @@ public function getPrivateKey() { /** * check if private key is set - * - * @return boolean */ - public function isPrivateKeySet() { + public function isPrivateKeySet(): bool { $key = $this->session->get('privateKey'); if (is_null($key)) { return false; @@ -94,17 +93,14 @@ public function isPrivateKeySet() { * * @note this should only be set on login */ - public function setPrivateKey($key) { + public function setPrivateKey(string $key): void { $this->session->set('privateKey', $key); } /** * store data needed for the decrypt all operation in the session - * - * @param string $user - * @param string $key */ - public function prepareDecryptAll($user, $key) { + public function prepareDecryptAll(string $user, string $key): void { $this->session->set('decryptAll', true); $this->session->set('decryptAllKey', $key); $this->session->set('decryptAllUid', $user); @@ -112,10 +108,8 @@ public function prepareDecryptAll($user, $key) { /** * check if we are in decrypt all mode - * - * @return bool */ - public function decryptAllModeActivated() { + public function decryptAllModeActivated(): bool { $decryptAll = $this->session->get('decryptAll'); return ($decryptAll === true); } @@ -123,10 +117,9 @@ public function decryptAllModeActivated() { /** * get uid used for decrypt all operation * - * @return string * @throws \Exception */ - public function getDecryptAllUid() { + public function getDecryptAllUid(): string { $uid = $this->session->get('decryptAllUid'); if (is_null($uid) && $this->decryptAllModeActivated()) { throw new \Exception('No uid found while in decrypt all mode'); @@ -140,10 +133,9 @@ public function getDecryptAllUid() { /** * get private key for decrypt all operation * - * @return string * @throws PrivateKeyMissingException */ - public function getDecryptAllKey() { + public function getDecryptAllKey(): string { $privateKey = $this->session->get('decryptAllKey'); if (is_null($privateKey) && $this->decryptAllModeActivated()) { throw new PrivateKeyMissingException('No private key found while in decrypt all mode'); @@ -157,7 +149,7 @@ public function getDecryptAllKey() { /** * remove keys from session */ - public function clear() { + public function clear(): void { $this->session->remove('publicSharePrivateKey'); $this->session->remove('privateKey'); $this->session->remove('encryptionInitialized'); diff --git a/apps/encryption/tests/Controller/RecoveryControllerTest.php b/apps/encryption/tests/Controller/RecoveryControllerTest.php index 955e9c7c964a8..3a490c33407c8 100644 --- a/apps/encryption/tests/Controller/RecoveryControllerTest.php +++ b/apps/encryption/tests/Controller/RecoveryControllerTest.php @@ -86,7 +86,7 @@ public function testChangeRecoveryPassword($password, $confirmPassword, $oldPass ->method('changeRecoveryKeyPassword') ->with($password, $oldPassword) ->willReturnMap([ - ['test', 'oldTestFail', false], + ['test', 'oldtestFail', false], ['test', 'oldtest', true] ]); diff --git a/apps/encryption/tests/Crypto/EncryptionTest.php b/apps/encryption/tests/Crypto/EncryptionTest.php index 4767d22339da5..a0e2f154d3d64 100644 --- a/apps/encryption/tests/Crypto/EncryptionTest.php +++ b/apps/encryption/tests/Crypto/EncryptionTest.php @@ -248,7 +248,7 @@ public function testBeginDecryptAll() { ->willReturn(true); $this->keyManagerMock->expects($this->once()) ->method('getFileKey') - ->with($path, 'user', null, true) + ->with($path, null, true) ->willReturn($fileKey); $this->instance->begin($path, 'user', 'r', [], []); diff --git a/apps/encryption/tests/KeyManagerTest.php b/apps/encryption/tests/KeyManagerTest.php index b408c9e180bb2..c0f81da39cdbb 100644 --- a/apps/encryption/tests/KeyManagerTest.php +++ b/apps/encryption/tests/KeyManagerTest.php @@ -350,20 +350,20 @@ public function dataTestGetFileKey() { return [ ['user1', false, 'privateKey', 'legacyKey', 'multiKeyDecryptResult'], ['user1', false, 'privateKey', '', 'multiKeyDecryptResult'], - ['user1', false, false, 'legacyKey', ''], - ['user1', false, false, '', ''], + ['user1', false, '', 'legacyKey', ''], + ['user1', false, '', '', ''], ['user1', true, 'privateKey', 'legacyKey', 'multiKeyDecryptResult'], ['user1', true, 'privateKey', '', 'multiKeyDecryptResult'], - ['user1', true, false, 'legacyKey', ''], - ['user1', true, false, '', ''], + ['user1', true, '', 'legacyKey', ''], + ['user1', true, '', '', ''], [null, false, 'privateKey', 'legacyKey', 'multiKeyDecryptResult'], [null, false, 'privateKey', '', 'multiKeyDecryptResult'], - [null, false, false, 'legacyKey', ''], - [null, false, false, '', ''], + [null, false, '', 'legacyKey', ''], + [null, false, '', '', ''], [null, true, 'privateKey', 'legacyKey', 'multiKeyDecryptResult'], [null, true, 'privateKey', '', 'multiKeyDecryptResult'], - [null, true, false, 'legacyKey', ''], - [null, true, false, '', ''], + [null, true, '', 'legacyKey', ''], + [null, true, '', '', ''], ]; } @@ -389,6 +389,7 @@ public function testGetFileKey($uid, $isMasterKeyEnabled, $privateKey, $encrypte } $this->invokePrivate($this->instance, 'masterKeyId', ['masterKeyId']); + $this->invokePrivate($this->instance, 'keyUid', [$uid]); $this->keyStorageMock->expects($this->exactly(2)) ->method('getFileKey') @@ -442,7 +443,7 @@ public function testGetFileKey($uid, $isMasterKeyEnabled, $privateKey, $encrypte } $this->assertSame($expected, - $this->instance->getFileKey($path, $uid, null) + $this->instance->getFileKey($path, null) ); } diff --git a/apps/encryption/tests/RecoveryTest.php b/apps/encryption/tests/RecoveryTest.php index a4f4ff7fd0296..49b850dd78a31 100644 --- a/apps/encryption/tests/RecoveryTest.php +++ b/apps/encryption/tests/RecoveryTest.php @@ -5,6 +5,7 @@ * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ + namespace OCA\Encryption\Tests; use OC\Files\View; @@ -124,17 +125,20 @@ public function testChangeRecoveryKeyPasswordSuccessful() { 'passwordOld')); $this->keyManagerMock->expects($this->once()) - ->method('getSystemPrivateKey'); + ->method('getSystemPrivateKey') + ->willReturn('privateKey'); $this->cryptMock->expects($this->once()) - ->method('decryptPrivateKey'); + ->method('decryptPrivateKey') + ->with('privateKey', 'passwordOld') + ->willReturn('decryptedPrivateKey'); $this->cryptMock->expects($this->once()) ->method('encryptPrivateKey') - ->willReturn(true); + ->with('decryptedPrivateKey', 'password') + ->willReturn('privateKey'); - $this->assertTrue($this->instance->changeRecoveryKeyPassword('password', - 'passwordOld')); + $this->assertTrue($this->instance->changeRecoveryKeyPassword('password', 'passwordOld')); } public function testChangeRecoveryKeyPasswordCouldNotDecryptPrivateRecoveryKey() { diff --git a/apps/encryption/tests/SessionTest.php b/apps/encryption/tests/SessionTest.php index 61e57f66611be..07218ef34a94b 100644 --- a/apps/encryption/tests/SessionTest.php +++ b/apps/encryption/tests/SessionTest.php @@ -76,7 +76,7 @@ public function testGetDecryptAllUidException() { public function testGetDecryptAllUidException2() { $this->expectException(\Exception::class); - $this->instance->prepareDecryptAll(null, 'key'); + $this->instance->prepareDecryptAll('', 'key'); $this->instance->getDecryptAllUid(); } @@ -95,7 +95,7 @@ public function testGetDecryptAllKeyException() { public function testGetDecryptAllKeyException2() { $this->expectException(\OCA\Encryption\Exceptions\PrivateKeyMissingException::class); - $this->instance->prepareDecryptAll('user', null); + $this->instance->prepareDecryptAll('user', ''); $this->instance->getDecryptAllKey(); } diff --git a/build/integration/features/bootstrap/CommandLine.php b/build/integration/features/bootstrap/CommandLine.php index 118da8a82cf4b..105874ad9e506 100644 --- a/build/integration/features/bootstrap/CommandLine.php +++ b/build/integration/features/bootstrap/CommandLine.php @@ -26,7 +26,7 @@ trait CommandLine { * @param []string $args OCC command, the part behind "occ". For example: "files:transfer-ownership" * @return int exit code */ - public function runOcc($args = []) { + public function runOcc($args = [], string $inputString = '') { $args = array_map(function ($arg) { return escapeshellarg($arg); }, $args); @@ -39,6 +39,10 @@ public function runOcc($args = []) { 2 => ['pipe', 'w'], ]; $process = proc_open('php console.php ' . $args, $descriptor, $pipes, $this->ocPath); + if ($inputString !== '') { + fwrite($pipes[0], $inputString . "\n"); + fclose($pipes[0]); + } $this->lastStdOut = stream_get_contents($pipes[1]); $this->lastStdErr = stream_get_contents($pipes[2]); $this->lastCode = proc_close($process); @@ -58,6 +62,14 @@ public function invokingTheCommand($cmd) { $this->runOcc($args); } + /** + * @Given /^invoking occ with "([^"]*)" with input "([^"]+)"$/ + */ + public function invokingTheCommandWith($cmd, $inputString) { + $args = explode(' ', $cmd); + $this->runOcc($args, $inputString); + } + /** * Find exception texts in stderr */ @@ -126,6 +138,13 @@ public function theCommandOutputContainsTheText($text) { Assert::assertStringContainsString($text, $this->lastStdOut, 'The command did not output the expected text on stdout'); } + /** + * @Then /^the command output does not contain the text "([^"]*)"$/ + */ + public function theCommandOutputDoesNotContainTheText($text) { + Assert::assertStringNotContainsString($text, $this->lastStdOut, 'The command did output the not expected text on stdout'); + } + /** * @Then /^the command error output contains the text "([^"]*)"$/ */ diff --git a/build/integration/files_features/encryption.feature b/build/integration/files_features/encryption.feature new file mode 100644 index 0000000000000..d961f15267151 --- /dev/null +++ b/build/integration/files_features/encryption.feature @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +Feature: encryption + + Scenario: encryption tests + # Setup encryption + Given using new dav path + And user "user0" exists + And User "user0" uploads file with content "BLABLABLA" to "/non-encrypted.txt" + And invoking occ with "app:enable encryption" + And the command was successful + And invoking occ with "encryption:enable" + And the command was successful + And As an "user0" + And User "user0" uploads file with content "BLABLABLA" to "/encrypted.txt" + # Check both encrypted and non-encrypted files can be read + When Downloading file "/encrypted.txt" with range "bytes=0-8" + Then Downloaded content should be "BLABLABLA" + When Downloading file "/non-encrypted.txt" with range "bytes=0-8" + Then Downloaded content should be "BLABLABLA" + When invoking occ with "info:file user0/files/encrypted.txt" + And the command was successful + Then the command output contains the text "server-side encrypted: yes" + When invoking occ with "info:file user0/files/non-encrypted.txt" + And the command was successful + Then the command output does not contain the text "server-side encrypted: yes" + # Run encryption:encrypt-all and checks that non-encrypted file gets encrypted + When invoking occ with "encryption:encrypt-all" with input "y" + And the command was successful + And invoking occ with "info:file user0/files/non-encrypted.txt" + And the command was successful + Then the command output contains the text "server-side encrypted: yes" + And Downloading file "/non-encrypted.txt" with range "bytes=0-8" + And Downloaded content should be "BLABLABLA" + # Run encryption:decrypt-all and checks that files gets decrypted + When invoking occ with "encryption:decrypt-all" with input "y" + And the command was successful + And invoking occ with "info:file user0/files/non-encrypted.txt" + And the command was successful + Then the command output does not contain the text "server-side encrypted: yes" + And Downloading file "/non-encrypted.txt" with range "bytes=0-8" + And Downloaded content should be "BLABLABLA" diff --git a/lib/private/Encryption/DecryptAll.php b/lib/private/Encryption/DecryptAll.php index 0007467298ce7..26dcd56802bbe 100644 --- a/lib/private/Encryption/DecryptAll.php +++ b/lib/private/Encryption/DecryptAll.php @@ -1,10 +1,13 @@ > files which couldn't be decrypted */ + protected array $failed = []; public function __construct( protected IManager $encryptionManager, protected IUserManager $userManager, protected View $rootView ) { - $this->failed = []; } /** * start to decrypt all files * - * @param InputInterface $input - * @param OutputInterface $output * @param string $user which users data folder should be decrypted, default = all users - * @return bool * @throws \Exception */ - public function decryptAll(InputInterface $input, OutputInterface $output, $user = '') { - $this->input = $input; - $this->output = $output; - + public function decryptAll(InputInterface $input, OutputInterface $output, string $user = ''): bool { if ($user !== '' && $this->userManager->userExists($user) === false) { - $this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again'); + $output->writeln('User "' . $user . '" does not exist. Please check the username and try again'); return false; } - $this->output->writeln('prepare encryption modules...'); - if ($this->prepareEncryptionModules($user) === false) { + $output->writeln('prepare encryption modules...'); + if ($this->prepareEncryptionModules($input, $output, $user) === false) { return false; } - $this->output->writeln(' done.'); + $output->writeln(' done.'); - $this->decryptAllUsersFiles($user); + $this->failed = []; + $this->decryptAllUsersFiles($output, $user); + /** @psalm-suppress RedundantCondition $this->failed is modified by decryptAllUsersFiles, not clear why psalm fails to see it */ if (empty($this->failed)) { - $this->output->writeln('all files could be decrypted successfully!'); + $output->writeln('all files could be decrypted successfully!'); } else { - $this->output->writeln('Files for following users couldn\'t be decrypted, '); - $this->output->writeln('maybe the user is not set up in a way that supports this operation: '); + $output->writeln('Files for following users couldn\'t be decrypted, '); + $output->writeln('maybe the user is not set up in a way that supports this operation: '); foreach ($this->failed as $uid => $paths) { - $this->output->writeln(' ' . $uid); + $output->writeln(' ' . $uid); foreach ($paths as $path) { - $this->output->writeln(' ' . $path); + $output->writeln(' ' . $path); } } - $this->output->writeln(''); + $output->writeln(''); } return true; @@ -79,21 +71,18 @@ public function decryptAll(InputInterface $input, OutputInterface $output, $user /** * prepare encryption modules to perform the decrypt all function - * - * @param $user - * @return bool */ - protected function prepareEncryptionModules($user) { + protected function prepareEncryptionModules(InputInterface $input, OutputInterface $output, string $user): bool { // prepare all encryption modules for decrypt all $encryptionModules = $this->encryptionManager->getEncryptionModules(); foreach ($encryptionModules as $moduleDesc) { /** @var IEncryptionModule $module */ $module = call_user_func($moduleDesc['callback']); - $this->output->writeln(''); - $this->output->writeln('Prepare "' . $module->getDisplayName() . '"'); - $this->output->writeln(''); - if ($module->prepareDecryptAll($this->input, $this->output, $user) === false) { - $this->output->writeln('Module "' . $moduleDesc['displayName'] . '" does not support the functionality to decrypt all files again or the initialization of the module failed!'); + $output->writeln(''); + $output->writeln('Prepare "' . $module->getDisplayName() . '"'); + $output->writeln(''); + if ($module->prepareDecryptAll($input, $output, $user) === false) { + $output->writeln('Module "' . $moduleDesc['displayName'] . '" does not support the functionality to decrypt all files again or the initialization of the module failed!'); return false; } } @@ -106,12 +95,12 @@ protected function prepareEncryptionModules($user) { * * @param string $user which users files should be decrypted, default = all users */ - protected function decryptAllUsersFiles($user = '') { - $this->output->writeln("\n"); + protected function decryptAllUsersFiles(OutputInterface $output, string $user = ''): void { + $output->writeln("\n"); $userList = []; if ($user === '') { - $fetchUsersProgress = new ProgressBar($this->output); + $fetchUsersProgress = new ProgressBar($output); $fetchUsersProgress->setFormat(" %message% \n [%bar%]"); $fetchUsersProgress->start(); $fetchUsersProgress->setMessage('Fetch list of users...'); @@ -135,9 +124,9 @@ protected function decryptAllUsersFiles($user = '') { $userList[] = $user; } - $this->output->writeln("\n\n"); + $output->writeln("\n\n"); - $progress = new ProgressBar($this->output); + $progress = new ProgressBar($output); $progress->setFormat(" %message% \n [%bar%]"); $progress->start(); $progress->setMessage('starting to decrypt files...'); @@ -154,17 +143,13 @@ protected function decryptAllUsersFiles($user = '') { $progress->setMessage('starting to decrypt files... finished'); $progress->finish(); - $this->output->writeln("\n\n"); + $output->writeln("\n\n"); } /** * encrypt files from the given user - * - * @param string $uid - * @param ProgressBar $progress - * @param string $userCount */ - protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) { + protected function decryptUsersFiles(string $uid, ProgressBar $progress, string $userCount): void { $this->setupUserFS($uid); $directories = []; $directories[] = '/' . $uid . '/files'; @@ -207,11 +192,8 @@ protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) { /** * encrypt file - * - * @param string $path - * @return bool */ - protected function decryptFile($path) { + protected function decryptFile(string $path): bool { // skip already decrypted files $fileInfo = $this->rootView->getFileInfo($path); if ($fileInfo !== false && !$fileInfo->isEncrypted()) { @@ -237,20 +219,15 @@ protected function decryptFile($path) { /** * get current timestamp - * - * @return int */ - protected function getTimestamp() { + protected function getTimestamp(): int { return time(); } - /** * setup user file system - * - * @param string $uid */ - protected function setupUserFS($uid) { + protected function setupUserFS(string $uid): void { \OC_Util::tearDownFS(); \OC_Util::setupFS($uid); } diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 112720bde7207..7e6da595c7a40 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -644,6 +644,13 @@ protected function hasEncryptionWrapper(): bool { return $this->storage->instanceOfStorage(Encryption::class); } + protected function shouldEncrypt(string $targetPath): bool { + if (!$this->storage->instanceOfStorage(Encryption::class)) { + return false; + } + return $this->storage->shouldEncrypt($targetPath); + } + /** * Move a file or folder in the cache * @@ -1158,7 +1165,9 @@ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, str $data = $this->cacheEntryToArray($sourceEntry); // when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark - if ($sourceCache instanceof Cache && $sourceCache->hasEncryptionWrapper() && !$this->hasEncryptionWrapper()) { + if ($sourceCache instanceof Cache + && $sourceCache->hasEncryptionWrapper() + && !$this->shouldEncrypt($targetPath)) { $data['encrypted'] = 0; } diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 78a96c572962c..8d8a7b7b98861 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -446,7 +446,7 @@ public function fopen($path, $mode) { } // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt - if (!$encryptionEnabled || !$this->shouldEncrypt($path)) { + if (!$this->shouldEncrypt($path)) { if (!$targetExists || !$targetIsEncrypted) { $shouldEncrypt = false; } diff --git a/tests/lib/Encryption/DecryptAllTest.php b/tests/lib/Encryption/DecryptAllTest.php index d3cae0ee1fd9d..2097fc205b2ac 100644 --- a/tests/lib/Encryption/DecryptAllTest.php +++ b/tests/lib/Encryption/DecryptAllTest.php @@ -1,5 +1,7 @@ userManager->expects($this->never())->method('userExists'); } - /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject | $instance */ + /** @var DecryptAll&MockObject $instance */ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll') ->setConstructorArgs( [ @@ -119,13 +126,13 @@ public function testDecryptAll($prepareResult, $user, $userExistsChecked) { $instance->expects($this->once()) ->method('prepareEncryptionModules') - ->with($user) + ->with($this->inputInterface, $this->outputInterface, $user) ->willReturn($prepareResult); if ($prepareResult) { $instance->expects($this->once()) ->method('decryptAllUsersFiles') - ->with($user); + ->with($this->outputInterface, $user); } else { $instance->expects($this->never())->method('decryptAllUsersFiles'); } @@ -182,7 +189,7 @@ public function testPrepareEncryptionModules($success) { ->willReturn([$moduleDescription]); $this->assertSame($success, - $this->invokePrivate($this->instance, 'prepareEncryptionModules', [$user]) + $this->invokePrivate($this->instance, 'prepareEncryptionModules', [$this->inputInterface, $this->outputInterface, $user]) ); } @@ -202,9 +209,6 @@ public function testDecryptAllUsersFiles($user) { ->setMethods(['decryptUsersFiles']) ->getMock(); - $this->invokePrivate($instance, 'input', [$this->inputInterface]); - $this->invokePrivate($instance, 'output', [$this->outputInterface]); - if (empty($user)) { $this->userManager->expects($this->once()) ->method('getBackends') @@ -224,7 +228,7 @@ public function testDecryptAllUsersFiles($user) { ->with($user); } - $this->invokePrivate($instance, 'decryptAllUsersFiles', [$user]); + $this->invokePrivate($instance, 'decryptAllUsersFiles', [$this->outputInterface, $user]); } public function dataTestDecryptAllUsersFiles() { @@ -312,7 +316,7 @@ function ($path) { public function testDecryptFile($isEncrypted) { $path = 'test.txt'; - /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */ + /** @var DecryptAll&MockObject $instance */ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll') ->setConstructorArgs( [ @@ -352,7 +356,7 @@ public function testDecryptFile($isEncrypted) { public function testDecryptFileFailure() { $path = 'test.txt'; - /** @var DecryptAll | \PHPUnit\Framework\MockObject\MockObject $instance */ + /** @var DecryptAll&MockObject $instance */ $instance = $this->getMockBuilder('OC\Encryption\DecryptAll') ->setConstructorArgs( [ diff --git a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php index 6866b30fbe9c3..be9b51182a92d 100644 --- a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php +++ b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php @@ -985,13 +985,15 @@ public function testShouldEncrypt( ->setMethods(['getFullPath', 'getEncryptionModule']) ->getMock(); + $encryptionManager->method('isEnabled')->willReturn($encryptionEnabled); + if ($encryptionModule === true) { /** @var IEncryptionModule|\PHPUnit\Framework\MockObject\MockObject $encryptionModule */ $encryptionModule = $this->createMock(IEncryptionModule::class); } $wrapper->method('getFullPath')->with($path)->willReturn($fullPath); - $wrapper->expects($encryptMountPoint ? $this->once() : $this->never()) + $wrapper->expects(($encryptionEnabled && $encryptMountPoint) ? $this->once() : $this->never()) ->method('getEncryptionModule') ->with($fullPath) ->willReturnCallback( @@ -1002,7 +1004,8 @@ function () use ($encryptionModule) { return $encryptionModule; } ); - $mount->expects($this->once())->method('getOption')->with('encrypt', true) + $mount->expects($encryptionEnabled ? $this->once() : $this->never()) + ->method('getOption')->with('encrypt', true) ->willReturn($encryptMountPoint); if ($encryptionModule !== null && $encryptionModule !== false) { @@ -1026,11 +1029,12 @@ function () use ($encryptionModule) { public function dataTestShouldEncrypt() { return [ - [false, false, false, false], - [true, false, false, false], - [true, true, false, false], - [true, true, true, true], - [true, null, false, true], + [true, false, false, false, false], + [true, true, false, false, false], + [true, true, true, false, false], + [true, true, true, true, true], + [true, true, null, false, true], + [false, true, true, true, false], ]; } } diff --git a/tests/lib/Traits/EncryptionTrait.php b/tests/lib/Traits/EncryptionTrait.php index 4d6e0eedb6b57..6878b670ee16b 100644 --- a/tests/lib/Traits/EncryptionTrait.php +++ b/tests/lib/Traits/EncryptionTrait.php @@ -77,6 +77,7 @@ protected function setupForUser($name, $password) { $encryptionManager = $container->query(IManager::class); $this->encryptionApp->setUp($encryptionManager); $keyManager->init($name, $password); + $this->invokePrivate($keyManager, 'keyUid', [$name]); } protected function postLogin() {