Browse Source
Merge pull request #18938 from owncloud/occ_decrypt_all2
Merge pull request #18938 from owncloud/occ_decrypt_all2
occ script to disable encryption and to decrypt all files againremotes/origin/db-empty-migrate
15 changed files with 1482 additions and 4 deletions
-
15apps/encryption/appinfo/application.php
-
143apps/encryption/lib/crypto/decryptall.php
-
36apps/encryption/lib/crypto/encryption.php
-
60apps/encryption/lib/session.php
-
55apps/encryption/tests/lib/SessionTest.php
-
125apps/encryption/tests/lib/crypto/decryptalltest.php
-
78apps/encryption/tests/lib/crypto/encryptionTest.php
-
148core/command/encryption/decryptall.php
-
7core/register_command.php
-
268lib/private/encryption/decryptall.php
-
11lib/public/encryption/iencryptionmodule.php
-
215tests/core/command/encryption/decryptalltest.php
-
321tests/lib/encryption/decryptalltest.php
-
2tests/lib/files/storage/wrapper/encryption.php
-
2tests/lib/files/stream/encryption.php
@ -0,0 +1,143 @@ |
|||
<?php |
|||
/** |
|||
* @author Björn Schießle <schiessle@owncloud.com> |
|||
* |
|||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace OCA\Encryption\Crypto; |
|||
|
|||
|
|||
use OCA\Encryption\KeyManager; |
|||
use OCA\Encryption\Session; |
|||
use OCA\Encryption\Util; |
|||
use Symfony\Component\Console\Helper\QuestionHelper; |
|||
use Symfony\Component\Console\Input\InputInterface; |
|||
use Symfony\Component\Console\Output\OutputInterface; |
|||
use Symfony\Component\Console\Question\ConfirmationQuestion; |
|||
use Symfony\Component\Console\Question\Question; |
|||
|
|||
class DecryptAll { |
|||
|
|||
/** @var Util */ |
|||
protected $util; |
|||
|
|||
/** @var QuestionHelper */ |
|||
protected $questionHelper; |
|||
|
|||
/** @var Crypt */ |
|||
protected $crypt; |
|||
|
|||
/** @var KeyManager */ |
|||
protected $keyManager; |
|||
|
|||
/** @var Session */ |
|||
protected $session; |
|||
|
|||
/** |
|||
* @param Util $util |
|||
* @param KeyManager $keyManager |
|||
* @param Crypt $crypt |
|||
* @param Session $session |
|||
* @param QuestionHelper $questionHelper |
|||
*/ |
|||
public function __construct( |
|||
Util $util, |
|||
KeyManager $keyManager, |
|||
Crypt $crypt, |
|||
Session $session, |
|||
QuestionHelper $questionHelper |
|||
) { |
|||
$this->util = $util; |
|||
$this->keyManager = $keyManager; |
|||
$this->crypt = $crypt; |
|||
$this->session = $session; |
|||
$this->questionHelper = $questionHelper; |
|||
} |
|||
|
|||
/** |
|||
* prepare encryption module to decrypt all files |
|||
* |
|||
* @param InputInterface $input |
|||
* @param OutputInterface $output |
|||
* @param $user |
|||
* @return bool |
|||
*/ |
|||
public function prepare(InputInterface $input, OutputInterface $output, $user) { |
|||
|
|||
$question = new Question('Please enter the recovery key password: '); |
|||
$recoveryKeyId = $this->keyManager->getRecoveryKeyId(); |
|||
|
|||
if (!empty($user)) { |
|||
$questionUseLoginPassword = new ConfirmationQuestion( |
|||
'Do you want to use the users login password to decrypt all files? (y/n) ', |
|||
false |
|||
); |
|||
$useLoginPassword = $this->questionHelper->ask($input, $output, $questionUseLoginPassword); |
|||
if ($useLoginPassword) { |
|||
$question = new Question('Please enter the users login password: '); |
|||
} else if ($this->util->isRecoveryEnabledForUser($user) === false) { |
|||
$output->writeln('No recovery key available for user ' . $user); |
|||
return false; |
|||
} else { |
|||
$user = $recoveryKeyId; |
|||
} |
|||
} else { |
|||
$user = $recoveryKeyId; |
|||
} |
|||
|
|||
$question->setHidden(true); |
|||
$question->setHiddenFallback(false); |
|||
$password = $this->questionHelper->ask($input, $output, $question); |
|||
$privateKey = $this->getPrivateKey($user, $password); |
|||
if ($privateKey !== false) { |
|||
$this->updateSession($user, $privateKey); |
|||
return true; |
|||
} else { |
|||
$output->writeln('Could not decrypt private key, maybe you entered the wrong password?'); |
|||
} |
|||
|
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* get the private key which will be used to decrypt all files |
|||
* |
|||
* @param string $user |
|||
* @param string $password |
|||
* @return bool|string |
|||
* @throws \OCA\Encryption\Exceptions\PrivateKeyMissingException |
|||
*/ |
|||
protected function getPrivateKey($user, $password) { |
|||
$recoveryKeyId = $this->keyManager->getRecoveryKeyId(); |
|||
if ($user === $recoveryKeyId) { |
|||
$recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId); |
|||
$privateKey = $this->crypt->decryptPrivateKey($recoveryKey, $password); |
|||
} else { |
|||
$userKey = $this->keyManager->getPrivateKey($user); |
|||
$privateKey = $this->crypt->decryptPrivateKey($userKey, $password, $user); |
|||
} |
|||
|
|||
return $privateKey; |
|||
} |
|||
|
|||
protected function updateSession($user, $privateKey) { |
|||
$this->session->prepareDecryptAll($user, $privateKey); |
|||
} |
|||
} |
@ -0,0 +1,125 @@ |
|||
<?php |
|||
/** |
|||
* @author Björn Schießle <schiessle@owncloud.com> |
|||
* |
|||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace OCA\Encryption\Tests\lib\Crypto; |
|||
|
|||
|
|||
use OCA\Encryption\Crypto\Crypt; |
|||
use OCA\Encryption\Crypto\DecryptAll; |
|||
use OCA\Encryption\KeyManager; |
|||
use OCA\Encryption\Session; |
|||
use OCA\Encryption\Util; |
|||
use Symfony\Component\Console\Helper\QuestionHelper; |
|||
use Test\TestCase; |
|||
|
|||
class DecryptAllTest extends TestCase { |
|||
|
|||
/** @var DecryptAll */ |
|||
protected $instance; |
|||
|
|||
/** @var Util | \PHPUnit_Framework_MockObject_MockObject */ |
|||
protected $util; |
|||
|
|||
/** @var KeyManager | \PHPUnit_Framework_MockObject_MockObject */ |
|||
protected $keyManager; |
|||
|
|||
/** @var Crypt | \PHPUnit_Framework_MockObject_MockObject */ |
|||
protected $crypt; |
|||
|
|||
/** @var Session | \PHPUnit_Framework_MockObject_MockObject */ |
|||
protected $session; |
|||
|
|||
/** @var QuestionHelper | \PHPUnit_Framework_MockObject_MockObject */ |
|||
protected $questionHelper; |
|||
|
|||
public function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->util = $this->getMockBuilder('OCA\Encryption\Util') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->keyManager = $this->getMockBuilder('OCA\Encryption\KeyManager') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->crypt = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->session = $this->getMockBuilder('OCA\Encryption\Session') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->questionHelper = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper') |
|||
->disableOriginalConstructor()->getMock(); |
|||
|
|||
$this->instance = new DecryptAll( |
|||
$this->util, |
|||
$this->keyManager, |
|||
$this->crypt, |
|||
$this->session, |
|||
$this->questionHelper |
|||
); |
|||
} |
|||
|
|||
public function testUpdateSession() { |
|||
$this->session->expects($this->once())->method('prepareDecryptAll') |
|||
->with('user1', 'key1'); |
|||
|
|||
$this->invokePrivate($this->instance, 'updateSession', ['user1', 'key1']); |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider dataTestGetPrivateKey |
|||
* |
|||
* @param string $user |
|||
* @param string $recoveryKeyId |
|||
*/ |
|||
public function testGetPrivateKey($user, $recoveryKeyId) { |
|||
$password = 'passwd'; |
|||
$recoveryKey = 'recoveryKey'; |
|||
$userKey = 'userKey'; |
|||
$unencryptedKey = 'unencryptedKey'; |
|||
|
|||
$this->keyManager->expects($this->any())->method('getRecoveryKeyId') |
|||
->willReturn($recoveryKeyId); |
|||
|
|||
if ($user === $recoveryKeyId) { |
|||
$this->keyManager->expects($this->once())->method('getSystemPrivateKey') |
|||
->with($recoveryKeyId)->willReturn($recoveryKey); |
|||
$this->keyManager->expects($this->never())->method('getPrivateKey'); |
|||
$this->crypt->expects($this->once())->method('decryptPrivateKey') |
|||
->with($recoveryKey, $password)->willReturn($unencryptedKey); |
|||
} else { |
|||
$this->keyManager->expects($this->never())->method('getSystemPrivateKey'); |
|||
$this->keyManager->expects($this->once())->method('getPrivateKey') |
|||
->with($user)->willReturn($userKey); |
|||
$this->crypt->expects($this->once())->method('decryptPrivateKey') |
|||
->with($userKey, $password, $user)->willReturn($unencryptedKey); |
|||
} |
|||
|
|||
$this->assertSame($unencryptedKey, |
|||
$this->invokePrivate($this->instance, 'getPrivateKey', [$user, $password]) |
|||
); |
|||
} |
|||
|
|||
public function dataTestGetPrivateKey() { |
|||
return [ |
|||
['user1', 'recoveryKey'], |
|||
['recoveryKeyId', 'recoveryKeyId'] |
|||
]; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,148 @@ |
|||
<?php |
|||
/** |
|||
* @author Björn Schießle <schiessle@owncloud.com> |
|||
* |
|||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
namespace OC\Core\Command\Encryption; |
|||
|
|||
use OCP\App\IAppManager; |
|||
use OCP\Encryption\IManager; |
|||
use OCP\IConfig; |
|||
use Symfony\Component\Console\Command\Command; |
|||
use Symfony\Component\Console\Helper\QuestionHelper; |
|||
use Symfony\Component\Console\Input\InputArgument; |
|||
use Symfony\Component\Console\Input\InputInterface; |
|||
use Symfony\Component\Console\Output\OutputInterface; |
|||
use Symfony\Component\Console\Question\ConfirmationQuestion; |
|||
|
|||
class DecryptAll extends Command { |
|||
|
|||
/** @var IManager */ |
|||
protected $encryptionManager; |
|||
|
|||
/** @var IAppManager */ |
|||
protected $appManager; |
|||
|
|||
/** @var IConfig */ |
|||
protected $config; |
|||
|
|||
/** @var QuestionHelper */ |
|||
protected $questionHelper; |
|||
|
|||
/** @var bool */ |
|||
protected $wasTrashbinEnabled; |
|||
|
|||
/** @var bool */ |
|||
protected $wasSingleUserModeEnabled; |
|||
|
|||
/** @var \OC\Encryption\DecryptAll */ |
|||
protected $decryptAll; |
|||
|
|||
/** |
|||
* @param IManager $encryptionManager |
|||
* @param IAppManager $appManager |
|||
* @param IConfig $config |
|||
* @param \OC\Encryption\DecryptAll $decryptAll |
|||
* @param QuestionHelper $questionHelper |
|||
*/ |
|||
public function __construct( |
|||
IManager $encryptionManager, |
|||
IAppManager $appManager, |
|||
IConfig $config, |
|||
\OC\Encryption\DecryptAll $decryptAll, |
|||
QuestionHelper $questionHelper |
|||
) { |
|||
parent::__construct(); |
|||
|
|||
$this->appManager = $appManager; |
|||
$this->encryptionManager = $encryptionManager; |
|||
$this->config = $config; |
|||
$this->decryptAll = $decryptAll; |
|||
$this->questionHelper = $questionHelper; |
|||
|
|||
$this->wasTrashbinEnabled = $this->appManager->isEnabledForUser('files_trashbin'); |
|||
$this->wasSingleUserModeEnabled = $this->config->getSystemValue('singleUser', false); |
|||
$this->config->setSystemValue('singleUser', true); |
|||
$this->appManager->disableApp('files_trashbin'); |
|||
} |
|||
|
|||
public function __destruct() { |
|||
$this->config->setSystemValue('singleUser', $this->wasSingleUserModeEnabled); |
|||
if ($this->wasTrashbinEnabled) { |
|||
$this->appManager->enableApp('files_trashbin'); |
|||
} |
|||
} |
|||
|
|||
protected function configure() { |
|||
parent::configure(); |
|||
|
|||
$this->setName('encryption:decrypt-all'); |
|||
$this->setDescription( |
|||
'This will disable server-side encryption and decrypt all files for ' |
|||
. 'all users if it is supported by your encryption module. ' |
|||
. 'Please make sure that no user access his files during this process!' |
|||
); |
|||
$this->addArgument( |
|||
'user', |
|||
InputArgument::OPTIONAL, |
|||
'user for which you want to decrypt all files (optional)' |
|||
); |
|||
} |
|||
|
|||
protected function execute(InputInterface $input, OutputInterface $output) { |
|||
|
|||
try { |
|||
if ($this->encryptionManager->isEnabled() === true) { |
|||
$output->write('Disable server side encryption... '); |
|||
$this->config->setAppValue('core', 'encryption_enabled', 'no'); |
|||
$output->writeln('done.'); |
|||
} else { |
|||
$output->writeln('Server side encryption not enabled. Nothing to do.'); |
|||
return; |
|||
|
|||
} |
|||
|
|||
$output->writeln("\n"); |
|||
$output->writeln('You are about to start to decrypt all files stored in your ownCloud.'); |
|||
$output->writeln('It will depend on the encryption module and your setup if this is possible.'); |
|||
$output->writeln('Depending on the number and size of your files this can take some time'); |
|||
$output->writeln('Please make sure that no user access his files during this process!'); |
|||
$output->writeln(''); |
|||
$question = new ConfirmationQuestion('Do you really want to continue? (y/n) ', false); |
|||
if ($this->questionHelper->ask($input, $output, $question)) { |
|||
$user = $input->getArgument('user'); |
|||
$result = $this->decryptAll->decryptAll($input, $output, $user); |
|||
if ($result === false) { |
|||
$this->output->writeln(' aborted.'); |
|||
$this->config->setAppValue('core', 'encryption_enabled', 'yes'); |
|||
} |
|||
} else { |
|||
$output->write('Enable server side encryption... '); |
|||
$this->config->setAppValue('core', 'encryption_enabled', 'yes'); |
|||
$output->writeln('done.'); |
|||
$output->writeln('aborted'); |
|||
} |
|||
} catch (\Exception $e) { |
|||
// enable server side encryption again if something went wrong
|
|||
$this->config->setAppValue('core', 'encryption_enabled', 'yes'); |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,268 @@ |
|||
<?php |
|||
/** |
|||
* @author Björn Schießle <schiessle@owncloud.com> |
|||
* |
|||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace OC\Encryption; |
|||
|
|||
use OC\Encryption\Exceptions\DecryptionFailedException; |
|||
use OC\Files\View; |
|||
use \OCP\Encryption\IEncryptionModule; |
|||
use OCP\IUserManager; |
|||
use Symfony\Component\Console\Helper\ProgressBar; |
|||
use Symfony\Component\Console\Input\InputInterface; |
|||
use Symfony\Component\Console\Output\OutputInterface; |
|||
|
|||
class DecryptAll { |
|||
|
|||
/** @var OutputInterface */ |
|||
protected $output; |
|||
|
|||
/** @var InputInterface */ |
|||
protected $input; |
|||
|
|||
/** @var Manager */ |
|||
protected $encryptionManager; |
|||
|
|||
/** @var IUserManager */ |
|||
protected $userManager; |
|||
|
|||
/** @var View */ |
|||
protected $rootView; |
|||
|
|||
/** @var array files which couldn't be decrypted */ |
|||
protected $failed; |
|||
|
|||
/** |
|||
* @param Manager $encryptionManager |
|||
* @param IUserManager $userManager |
|||
* @param View $rootView |
|||
*/ |
|||
public function __construct( |
|||
Manager $encryptionManager, |
|||
IUserManager $userManager, |
|||
View $rootView |
|||
) { |
|||
$this->encryptionManager = $encryptionManager; |
|||
$this->userManager = $userManager; |
|||
$this->rootView = $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; |
|||
|
|||
$this->output->writeln('prepare encryption modules...'); |
|||
if ($this->prepareEncryptionModules($user) === false) { |
|||
return false; |
|||
} |
|||
$this->output->writeln(' done.'); |
|||
|
|||
$this->decryptAllUsersFiles($user); |
|||
|
|||
if (empty($this->failed)) { |
|||
$this->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: '); |
|||
foreach ($this->failed as $uid => $paths) { |
|||
$this->output->writeln(' ' . $uid); |
|||
} |
|||
$this->output->writeln(''); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* prepare encryption modules to perform the decrypt all function
|
|||
* |
|||
* @param $user |
|||
* @return bool |
|||
*/ |
|||
protected function prepareEncryptionModules($user) { |
|||
// prepare all encryption modules for decrypt all
|
|||
$encryptionModules = $this->encryptionManager->getEncryptionModules(); |
|||
foreach ($encryptionModules as $moduleDesc) { |
|||
/** @var IEncryptionModule $module */ |
|||
$module = call_user_func($moduleDesc['callback']); |
|||
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!'); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* iterate over all user and encrypt their files |
|||
* @param string $user which users files should be decrypted, default = all users |
|||
*/ |
|||
protected function decryptAllUsersFiles($user = '') { |
|||
|
|||
$this->output->writeln("\n"); |
|||
|
|||
$userList = []; |
|||
if (empty($user)) { |
|||
|
|||
$fetchUsersProgress = new ProgressBar($this->output); |
|||
$fetchUsersProgress->setFormat(" %message% \n [%bar%]"); |
|||
$fetchUsersProgress->start(); |
|||
$fetchUsersProgress->setMessage("Fetch list of users..."); |
|||
$fetchUsersProgress->advance(); |
|||
|
|||
foreach ($this->userManager->getBackends() as $backend) { |
|||
$limit = 500; |
|||
$offset = 0; |
|||
do { |
|||
$users = $backend->getUsers('', $limit, $offset); |
|||
foreach ($users as $user) { |
|||
$userList[] = $user; |
|||
} |
|||
$offset += $limit; |
|||
$fetchUsersProgress->advance(); |
|||
} while (count($users) >= $limit); |
|||
$fetchUsersProgress->setMessage("Fetch list of users... finished"); |
|||
$fetchUsersProgress->finish(); |
|||
} |
|||
} else { |
|||
$userList[] = $user; |
|||
} |
|||
|
|||
$this->output->writeln("\n\n"); |
|||
|
|||
$progress = new ProgressBar($this->output); |
|||
$progress->setFormat(" %message% \n [%bar%]"); |
|||
$progress->start(); |
|||
$progress->setMessage("starting to decrypt files..."); |
|||
$progress->advance(); |
|||
|
|||
$numberOfUsers = count($userList); |
|||
$userNo = 1; |
|||
foreach ($userList as $uid) { |
|||
$userCount = "$uid ($userNo of $numberOfUsers)"; |
|||
$this->decryptUsersFiles($uid, $progress, $userCount); |
|||
$userNo++; |
|||
} |
|||
|
|||
$progress->setMessage("starting to decrypt files... finished"); |
|||
$progress->finish(); |
|||
|
|||
$this->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) { |
|||
|
|||
$this->setupUserFS($uid); |
|||
$directories = array(); |
|||
$directories[] = '/' . $uid . '/files'; |
|||
|
|||
while($root = array_pop($directories)) { |
|||
$content = $this->rootView->getDirectoryContent($root); |
|||
foreach ($content as $file) { |
|||
$path = $root . '/' . $file['name']; |
|||
if ($this->rootView->is_dir($path)) { |
|||
$directories[] = $path; |
|||
continue; |
|||
} else { |
|||
try { |
|||
$progress->setMessage("decrypt files for user $userCount: $path"); |
|||
$progress->advance(); |
|||
if ($this->decryptFile($path) === false) { |
|||
$progress->setMessage("decrypt files for user $userCount: $path (already decrypted)"); |
|||
$progress->advance(); |
|||
} |
|||
} catch (\Exception $e) { |
|||
if (isset($this->failed[$uid])) { |
|||
$this->failed[$uid][] = $path; |
|||
} else { |
|||
$this->failed[$uid] = [$path]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* encrypt file |
|||
* |
|||
* @param string $path |
|||
* @return bool |
|||
*/ |
|||
protected function decryptFile($path) { |
|||
|
|||
$source = $path; |
|||
$target = $path . '.decrypted.' . $this->getTimestamp(); |
|||
|
|||
try { |
|||
$this->rootView->copy($source, $target); |
|||
$this->rootView->rename($target, $source); |
|||
} catch (DecryptionFailedException $e) { |
|||
if ($this->rootView->file_exists($target)) { |
|||
$this->rootView->unlink($target); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* get current timestamp |
|||
* |
|||
* @return int |
|||
*/ |
|||
protected function getTimestamp() { |
|||
return time(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* setup user file system |
|||
* |
|||
* @param string $uid |
|||
*/ |
|||
protected function setupUserFS($uid) { |
|||
\OC_Util::tearDownFS(); |
|||
\OC_Util::setupFS($uid); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,215 @@ |
|||
<?php |
|||
/** |
|||
* @author Björn Schießle <schiessle@owncloud.com> |
|||
* |
|||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace Tests\Core\Command\Encryption; |
|||
|
|||
|
|||
use OC\Core\Command\Encryption\DecryptAll; |
|||
use Test\TestCase; |
|||
|
|||
class DecryptAllTest extends TestCase { |
|||
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IConfig */ |
|||
protected $config; |
|||
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\Encryption\IManager */ |
|||
protected $encryptionManager; |
|||
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\App\IAppManager */ |
|||
protected $appManager; |
|||
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject | \Symfony\Component\Console\Input\InputInterface */ |
|||
protected $consoleInput; |
|||
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject | \Symfony\Component\Console\Output\OutputInterface */ |
|||
protected $consoleOutput; |
|||
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject | \Symfony\Component\Console\Helper\QuestionHelper */ |
|||
protected $questionHelper; |
|||
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Encryption\DecryptAll */ |
|||
protected $decryptAll; |
|||
|
|||
public function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->config = $this->getMockBuilder('OCP\IConfig') |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
$this->encryptionManager = $this->getMockBuilder('OCP\Encryption\IManager') |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
$this->appManager = $this->getMockBuilder('OCP\App\IAppManager') |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
$this->questionHelper = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper') |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
$this->decryptAll = $this->getMockBuilder('OC\Encryption\DecryptAll') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->consoleInput = $this->getMock('Symfony\Component\Console\Input\InputInterface'); |
|||
$this->consoleOutput = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); |
|||
|
|||
$this->config->expects($this->any()) |
|||
->method('getSystemValue') |
|||
->with('singleUser', false) |
|||
->willReturn(false); |
|||
$this->appManager->expects($this->any()) |
|||
->method('isEnabledForUser') |
|||
->with('files_trashbin')->willReturn(true); |
|||
|
|||
} |
|||
|
|||
public function testConstructDesctruct() { |
|||
// on construct we enable single-user-mode and disable the trash bin
|
|||
$this->config->expects($this->at(1)) |
|||
->method('setSystemValue') |
|||
->with('singleUser', true); |
|||
$this->appManager->expects($this->once()) |
|||
->method('disableApp') |
|||
->with('files_trashbin'); |
|||
|
|||
// on destruct wi disable single-user-mode again and enable the trash bin
|
|||
$this->config->expects($this->at(2)) |
|||
->method('setSystemValue') |
|||
->with('singleUser', false); |
|||
$this->appManager->expects($this->once()) |
|||
->method('enableApp') |
|||
->with('files_trashbin'); |
|||
|
|||
$instance = new DecryptAll( |
|||
$this->encryptionManager, |
|||
$this->appManager, |
|||
$this->config, |
|||
$this->decryptAll, |
|||
$this->questionHelper |
|||
); |
|||
|
|||
$this->assertTrue( |
|||
$this->invokePrivate($instance, 'wasTrashbinEnabled') |
|||
); |
|||
|
|||
$this->assertFalse( |
|||
$this->invokePrivate($instance, 'wasSingleUserModeEnabled') |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider dataTestExecute |
|||
*/ |
|||
public function testExecute($encryptionEnabled, $continue) { |
|||
|
|||
$instance = new DecryptAll( |
|||
$this->encryptionManager, |
|||
$this->appManager, |
|||
$this->config, |
|||
$this->decryptAll, |
|||
$this->questionHelper |
|||
); |
|||
|
|||
$this->encryptionManager->expects($this->once()) |
|||
->method('isEnabled') |
|||
->willReturn($encryptionEnabled); |
|||
|
|||
$this->consoleInput->expects($this->any()) |
|||
->method('getArgument') |
|||
->with('user') |
|||
->willReturn('user1'); |
|||
|
|||
if ($encryptionEnabled) { |
|||
$this->config->expects($this->at(0)) |
|||
->method('setAppValue') |
|||
->with('core', 'encryption_enabled', 'no'); |
|||
$this->questionHelper->expects($this->once()) |
|||
->method('ask') |
|||
->willReturn($continue); |
|||
if ($continue) { |
|||
$this->decryptAll->expects($this->once()) |
|||
->method('decryptAll') |
|||
->with($this->consoleInput, $this->consoleOutput, 'user1'); |
|||
} else { |
|||
$this->decryptAll->expects($this->never())->method('decryptAll'); |
|||
$this->config->expects($this->at(1)) |
|||
->method('setAppValue') |
|||
->with('core', 'encryption_enabled', 'yes'); |
|||
} |
|||
} else { |
|||
$this->config->expects($this->never())->method('setAppValue'); |
|||
$this->decryptAll->expects($this->never())->method('decryptAll'); |
|||
$this->questionHelper->expects($this->never())->method('ask'); |
|||
} |
|||
|
|||
$this->invokePrivate($instance, 'execute', [$this->consoleInput, $this->consoleOutput]); |
|||
} |
|||
|
|||
public function dataTestExecute() { |
|||
return [ |
|||
[true, true], |
|||
[true, false], |
|||
[false, true], |
|||
[false, false] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \Exception |
|||
*/ |
|||
public function testExecuteFailure() { |
|||
$instance = new DecryptAll( |
|||
$this->encryptionManager, |
|||
$this->appManager, |
|||
$this->config, |
|||
$this->decryptAll, |
|||
$this->questionHelper |
|||
); |
|||
|
|||
$this->config->expects($this->at(0)) |
|||
->method('setAppValue') |
|||
->with('core', 'encryption_enabled', 'no'); |
|||
|
|||
// make sure that we enable encryption again after a exception was thrown
|
|||
$this->config->expects($this->at(1)) |
|||
->method('setAppValue') |
|||
->with('core', 'encryption_enabled', 'yes'); |
|||
|
|||
$this->encryptionManager->expects($this->once()) |
|||
->method('isEnabled') |
|||
->willReturn(true); |
|||
|
|||
$this->consoleInput->expects($this->any()) |
|||
->method('getArgument') |
|||
->with('user') |
|||
->willReturn('user1'); |
|||
|
|||
$this->questionHelper->expects($this->once()) |
|||
->method('ask') |
|||
->willReturn(true); |
|||
|
|||
$this->decryptAll->expects($this->once()) |
|||
->method('decryptAll') |
|||
->with($this->consoleInput, $this->consoleOutput, 'user1') |
|||
->willReturnCallback(function() { throw new \Exception(); }); |
|||
|
|||
$this->invokePrivate($instance, 'execute', [$this->consoleInput, $this->consoleOutput]); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,321 @@ |
|||
<?php |
|||
/** |
|||
* @author Björn Schießle <schiessle@owncloud.com> |
|||
* |
|||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace Test\Encryption; |
|||
|
|||
|
|||
use OC\Encryption\DecryptAll; |
|||
use OC\Encryption\Exceptions\DecryptionFailedException; |
|||
use OC\Encryption\Manager; |
|||
use OC\Files\View; |
|||
use OCP\IUserManager; |
|||
use Test\TestCase; |
|||
|
|||
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 | \OCP\UserInterface */ |
|||
protected $userInterface; |
|||
|
|||
/** @var DecryptAll */ |
|||
protected $instance; |
|||
|
|||
public function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->userManager = $this->getMockBuilder('OCP\IUserManager') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->encryptionManager = $this->getMockBuilder('OC\Encryption\Manager') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->view = $this->getMockBuilder('OC\Files\View') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->inputInterface = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->outputInterface = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface') |
|||
->disableOriginalConstructor()->getMock(); |
|||
$this->userInterface = $this->getMockBuilder('OCP\UserInterface') |
|||
->disableOriginalConstructor()->getMock(); |
|||
|
|||
$this->outputInterface->expects($this->any())->method('getFormatter') |
|||
->willReturn($this->getMock('\Symfony\Component\Console\Formatter\OutputFormatterInterface')); |
|||
|
|||
$this->instance = new DecryptAll($this->encryptionManager, $this->userManager, $this->view); |
|||
|
|||
$this->invokePrivate($this->instance, 'input', [$this->inputInterface]); |
|||
$this->invokePrivate($this->instance, 'output', [$this->outputInterface]); |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider dataTrueFalse |
|||
*/ |
|||
public function testDecryptAll($prepareResult) { |
|||
|
|||
$user = 'user1'; |
|||
|
|||
/** @var DecryptAll | \PHPUnit_Framework_MockObject_MockObject | $instance */ |
|||
$instance = $this->getMockBuilder('OC\Encryption\DecryptAll') |
|||
->setConstructorArgs( |
|||
[ |
|||
$this->encryptionManager, |
|||
$this->userManager, |
|||
$this->view |
|||
] |
|||
) |
|||
->setMethods(['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); |
|||
} |
|||
|
|||
public function dataTrueFalse() { |
|||
return [ |
|||
[true], |
|||
[false] |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider dataTrueFalse |
|||
*/ |
|||
public function testPrepareEncryptionModules($success) { |
|||
|
|||
$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]) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @dataProvider dataTestDecryptAllUsersFiles |
|||
*/ |
|||
public function testDecryptAllUsersFiles($user) { |
|||
|
|||
/** @var DecryptAll | \PHPUnit_Framework_MockObject_MockObject | $instance */ |
|||
$instance = $this->getMockBuilder('OC\Encryption\DecryptAll') |
|||
->setConstructorArgs( |
|||
[ |
|||
$this->encryptionManager, |
|||
$this->userManager, |
|||
$this->view |
|||
] |
|||
) |
|||
->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') |
|||
->willReturn([$this->userInterface]); |
|||
$this->userInterface->expects($this->any()) |
|||
->method('getUsers') |
|||
->willReturn(['user1', 'user2']); |
|||
$instance->expects($this->at(0)) |
|||
->method('decryptUsersFiles') |
|||
->with('user1'); |
|||
$instance->expects($this->at(1)) |
|||
->method('decryptUsersFiles') |
|||
->with('user2'); |
|||
} else { |
|||
$instance->expects($this->once()) |
|||
->method('decryptUsersFiles') |
|||
->with($user); |
|||
} |
|||
|
|||
$this->invokePrivate($instance, 'decryptAllUsersFiles', [$user]); |
|||
} |
|||
|
|||
public function dataTestDecryptAllUsersFiles() { |
|||
return [ |
|||
['user1'], |
|||
[''] |
|||
]; |
|||
} |
|||
|
|||
public function testDecryptUsersFiles() { |
|||
/** @var DecryptAll | \PHPUnit_Framework_MockObject_MockObject $instance */ |
|||
$instance = $this->getMockBuilder('OC\Encryption\DecryptAll') |
|||
->setConstructorArgs( |
|||
[ |
|||
$this->encryptionManager, |
|||
$this->userManager, |
|||
$this->view |
|||
] |
|||
) |
|||
->setMethods(['decryptFile']) |
|||
->getMock(); |
|||
|
|||
$this->view->expects($this->at(0))->method('getDirectoryContent') |
|||
->with('/user1/files')->willReturn( |
|||
[ |
|||
['name' => 'foo', 'type'=>'dir'], |
|||
['name' => 'bar', 'type'=>'file'], |
|||
] |
|||
); |
|||
|
|||
$this->view->expects($this->at(3))->method('getDirectoryContent') |
|||
->with('/user1/files/foo')->willReturn( |
|||
[ |
|||
['name' => 'subfile', 'type'=>'file'] |
|||
] |
|||
); |
|||
|
|||
$this->view->expects($this->any())->method('is_dir') |
|||
->willReturnCallback( |
|||
function($path) { |
|||
if ($path === '/user1/files/foo') { |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
); |
|||
|
|||
$instance->expects($this->at(0)) |
|||
->method('decryptFile') |
|||
->with('/user1/files/bar'); |
|||
$instance->expects($this->at(1)) |
|||
->method('decryptFile') |
|||
->with('/user1/files/foo/subfile'); |
|||
|
|||
$progressBar = $this->getMockBuilder('Symfony\Component\Console\Helper\ProgressBar') |
|||
->disableOriginalConstructor()->getMock(); |
|||
|
|||
$this->invokePrivate($instance, 'decryptUsersFiles', ['user1', $progressBar, '']); |
|||
|
|||
} |
|||
|
|||
public function testDecryptFile() { |
|||
|
|||
$path = 'test.txt'; |
|||
|
|||
/** @var DecryptAll | \PHPUnit_Framework_MockObject_MockObject $instance */ |
|||
$instance = $this->getMockBuilder('OC\Encryption\DecryptAll') |
|||
->setConstructorArgs( |
|||
[ |
|||
$this->encryptionManager, |
|||
$this->userManager, |
|||
$this->view |
|||
] |
|||
) |
|||
->setMethods(['getTimestamp']) |
|||
->getMock(); |
|||
|
|||
$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); |
|||
|
|||
$this->assertTrue( |
|||
$this->invokePrivate($instance, 'decryptFile', [$path]) |
|||
); |
|||
} |
|||
|
|||
public function testDecryptFileFailure() { |
|||
$path = 'test.txt'; |
|||
|
|||
/** @var DecryptAll | \PHPUnit_Framework_MockObject_MockObject $instance */ |
|||
$instance = $this->getMockBuilder('OC\Encryption\DecryptAll') |
|||
->setConstructorArgs( |
|||
[ |
|||
$this->encryptionManager, |
|||
$this->userManager, |
|||
$this->view |
|||
] |
|||
) |
|||
->setMethods(['getTimestamp']) |
|||
->getMock(); |
|||
|
|||
$instance->expects($this->any())->method('getTimestamp')->willReturn(42); |
|||
|
|||
$this->view->expects($this->once()) |
|||
->method('copy') |
|||
->with($path, $path . '.decrypted.42') |
|||
->willReturnCallback(function() { 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]) |
|||
); |
|||
} |
|||
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue