Merge pull request #10718 from nextcloud/occ-recover-encryption
add occ command to recover encrypted files in case of password lost
This commit is contained in:
commit
3a44cce5cc
|
@ -14,7 +14,7 @@
|
|||
Please read the documentation to know all implications before you decide
|
||||
to enable server-side encryption.
|
||||
</description>
|
||||
<version>2.1.0</version>
|
||||
<version>2.2.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Bjoern Schiessle</author>
|
||||
<author>Clark Tomlinson</author>
|
||||
|
@ -43,6 +43,7 @@
|
|||
<commands>
|
||||
<command>OCA\Encryption\Command\EnableMasterKey</command>
|
||||
<command>OCA\Encryption\Command\DisableMasterKey</command>
|
||||
<command>OCA\Encryption\Command\RecoverUser</command>
|
||||
</commands>
|
||||
|
||||
<settings>
|
||||
|
|
|
@ -9,6 +9,7 @@ return array(
|
|||
'OCA\\Encryption\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\Encryption\\Command\\DisableMasterKey' => $baseDir . '/../lib/Command/DisableMasterKey.php',
|
||||
'OCA\\Encryption\\Command\\EnableMasterKey' => $baseDir . '/../lib/Command/EnableMasterKey.php',
|
||||
'OCA\\Encryption\\Command\\RecoverUser' => $baseDir . '/../lib/Command/RecoverUser.php',
|
||||
'OCA\\Encryption\\Controller\\RecoveryController' => $baseDir . '/../lib/Controller/RecoveryController.php',
|
||||
'OCA\\Encryption\\Controller\\SettingsController' => $baseDir . '/../lib/Controller/SettingsController.php',
|
||||
'OCA\\Encryption\\Controller\\StatusController' => $baseDir . '/../lib/Controller/StatusController.php',
|
||||
|
|
|
@ -24,6 +24,7 @@ class ComposerStaticInitEncryption
|
|||
'OCA\\Encryption\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\Encryption\\Command\\DisableMasterKey' => __DIR__ . '/..' . '/../lib/Command/DisableMasterKey.php',
|
||||
'OCA\\Encryption\\Command\\EnableMasterKey' => __DIR__ . '/..' . '/../lib/Command/EnableMasterKey.php',
|
||||
'OCA\\Encryption\\Command\\RecoverUser' => __DIR__ . '/..' . '/../lib/Command/RecoverUser.php',
|
||||
'OCA\\Encryption\\Controller\\RecoveryController' => __DIR__ . '/..' . '/../lib/Controller/RecoveryController.php',
|
||||
'OCA\\Encryption\\Controller\\SettingsController' => __DIR__ . '/..' . '/../lib/Controller/SettingsController.php',
|
||||
'OCA\\Encryption\\Controller\\StatusController' => __DIR__ . '/..' . '/../lib/Controller/StatusController.php',
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption\Command;
|
||||
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OC\User\NoUserException;
|
||||
use OCA\Encryption\Crypto\Crypt;
|
||||
use OCA\Encryption\KeyManager;
|
||||
use OCA\Encryption\Recovery;
|
||||
use OCA\Encryption\Util;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUserManager;
|
||||
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\Question;
|
||||
|
||||
class RecoverUser extends Command {
|
||||
|
||||
/** @var Util */
|
||||
protected $util;
|
||||
|
||||
/** @var IUserManager */
|
||||
protected $userManager;
|
||||
|
||||
/** @var QuestionHelper */
|
||||
protected $questionHelper;
|
||||
|
||||
/**
|
||||
* @param Util $util
|
||||
* @param IConfig $config
|
||||
* @param IUserManager $userManager
|
||||
* @param QuestionHelper $questionHelper
|
||||
*/
|
||||
public function __construct(Util $util,
|
||||
IConfig $config,
|
||||
IUserManager $userManager,
|
||||
QuestionHelper $questionHelper) {
|
||||
|
||||
$this->util = $util;
|
||||
$this->questionHelper = $questionHelper;
|
||||
$this->userManager = $userManager;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure() {
|
||||
$this
|
||||
->setName('encryption:recover-user')
|
||||
->setDescription('Recover user data in case of password lost. This only works if the user enabled the recovery key.');
|
||||
|
||||
$this->addArgument(
|
||||
'user',
|
||||
InputArgument::REQUIRED,
|
||||
'user which should be recovered'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$isMasterKeyEnabled = $this->util->isMasterKeyEnabled();
|
||||
|
||||
if($isMasterKeyEnabled) {
|
||||
$output->writeln('You use the master key, no individual user recovery needed.');
|
||||
return;
|
||||
}
|
||||
|
||||
$uid = $input->getArgument('user');
|
||||
$userExists = $this->userManager->userExists($uid);
|
||||
if ($userExists === false) {
|
||||
$output->writeln('User "' . $uid . '" unknown.');
|
||||
return;
|
||||
}
|
||||
|
||||
$recoveryKeyEnabled = $this->util->isRecoveryEnabledForUser($uid);
|
||||
if($recoveryKeyEnabled === false) {
|
||||
$output->writeln('Recovery key is not enabled for: ' . $uid);
|
||||
return;
|
||||
}
|
||||
|
||||
$question = new Question('Please enter the recovery key password: ');
|
||||
$question->setHidden(true);
|
||||
$question->setHiddenFallback(false);
|
||||
$recoveryPassword = $this->questionHelper->ask($input, $output, $question);
|
||||
|
||||
$question = new Question('Please enter the new login password for the user: ');
|
||||
$question->setHidden(true);
|
||||
$question->setHiddenFallback(false);
|
||||
$newLoginPassword = $this->questionHelper->ask($input, $output, $question);
|
||||
|
||||
$output->write('Start to recover users files... This can take some time...');
|
||||
$this->userManager->get($uid)->setPassword($newLoginPassword, $recoveryPassword);
|
||||
$output->writeln('Done.');
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,7 @@ namespace OCA\Encryption\Hooks;
|
|||
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Util as OCUtil;
|
||||
use OCA\Encryption\Hooks\Contracts\IHook;
|
||||
|
@ -252,11 +253,12 @@ class UserHooks implements IHook {
|
|||
}
|
||||
|
||||
// Get existing decrypted private key
|
||||
$privateKey = $this->session->getPrivateKey();
|
||||
$user = $this->user->getUser();
|
||||
|
||||
// current logged in user changes his own password
|
||||
if ($user && $params['uid'] === $user->getUID() && $privateKey) {
|
||||
if ($user && $params['uid'] === $user->getUID()) {
|
||||
|
||||
$privateKey = $this->session->getPrivateKey();
|
||||
|
||||
// Encrypt private key with new user pwd as passphrase
|
||||
$encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password'], $params['uid']);
|
||||
|
@ -277,6 +279,18 @@ class UserHooks implements IHook {
|
|||
$this->initMountPoints($user);
|
||||
$recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
|
||||
|
||||
$recoveryKeyId = $this->keyManager->getRecoveryKeyId();
|
||||
$recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId);
|
||||
try {
|
||||
$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword);
|
||||
} catch (\Exception $e) {
|
||||
$decryptedRecoveryKey = false;
|
||||
}
|
||||
if ($decryptedRecoveryKey === false) {
|
||||
$message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.';
|
||||
throw new GenericEncryptionException($message, $message);
|
||||
}
|
||||
|
||||
// we generate new keys if...
|
||||
// ...we have a recovery password and the user enabled the recovery key
|
||||
// ...encryption was activated for the first time (no keys exists)
|
||||
|
|
|
@ -210,9 +210,9 @@ class UserHooksTest extends TestCase {
|
|||
}
|
||||
|
||||
public function testSetPassphrase() {
|
||||
$this->sessionMock->expects($this->exactly(4))
|
||||
$this->sessionMock->expects($this->once())
|
||||
->method('getPrivateKey')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
->willReturn(true);
|
||||
|
||||
$this->cryptMock->expects($this->exactly(4))
|
||||
->method('encryptPrivateKey')
|
||||
|
@ -236,7 +236,7 @@ class UserHooksTest extends TestCase {
|
|||
|
||||
$this->recoveryMock->expects($this->exactly(3))
|
||||
->method('isRecoveryEnabledForUser')
|
||||
->with('testUser')
|
||||
->with('testUser1')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
|
||||
|
||||
|
@ -257,13 +257,15 @@ class UserHooksTest extends TestCase {
|
|||
|
||||
$this->instance->expects($this->exactly(3))->method('initMountPoints');
|
||||
|
||||
$this->params['uid'] = 'testUser1';
|
||||
|
||||
// Test first if statement
|
||||
$this->assertNull($this->instance->setPassphrase($this->params));
|
||||
|
||||
// Test Second if conditional
|
||||
$this->keyManagerMock->expects($this->exactly(2))
|
||||
->method('userHasKeys')
|
||||
->with('testUser')
|
||||
->with('testUser1')
|
||||
->willReturn(true);
|
||||
|
||||
$this->assertNull($this->instance->setPassphrase($this->params));
|
||||
|
@ -271,7 +273,7 @@ class UserHooksTest extends TestCase {
|
|||
// Test third and final if condition
|
||||
$this->utilMock->expects($this->once())
|
||||
->method('userHasFiles')
|
||||
->with('testUser')
|
||||
->with('testUser1')
|
||||
->willReturn(false);
|
||||
|
||||
$this->cryptMock->expects($this->once())
|
||||
|
@ -282,7 +284,7 @@ class UserHooksTest extends TestCase {
|
|||
|
||||
$this->recoveryMock->expects($this->once())
|
||||
->method('recoverUsersFiles')
|
||||
->with('password', 'testUser');
|
||||
->with('password', 'testUser1');
|
||||
|
||||
$this->assertNull($this->instance->setPassphrase($this->params));
|
||||
}
|
||||
|
@ -297,9 +299,6 @@ class UserHooksTest extends TestCase {
|
|||
}
|
||||
|
||||
public function testSetPasswordNoUser() {
|
||||
$this->sessionMock->expects($this->once())
|
||||
->method('getPrivateKey')
|
||||
->willReturn(true);
|
||||
|
||||
$userSessionMock = $this->getMockBuilder(IUserSession::class)
|
||||
->disableOriginalConstructor()
|
||||
|
|
Loading…
Reference in New Issue