aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/encryption/appinfo/application.php15
-rw-r--r--apps/encryption/lib/crypto/decryptall.php143
-rw-r--r--apps/encryption/lib/crypto/encryption.php36
-rw-r--r--apps/encryption/lib/session.php60
-rw-r--r--apps/encryption/tests/lib/SessionTest.php55
-rw-r--r--apps/encryption/tests/lib/crypto/decryptalltest.php125
-rw-r--r--apps/encryption/tests/lib/crypto/encryptionTest.php78
-rw-r--r--core/command/encryption/decryptall.php148
-rw-r--r--core/register_command.php7
-rw-r--r--lib/private/encryption/decryptall.php268
-rw-r--r--lib/public/encryption/iencryptionmodule.php11
-rw-r--r--tests/core/command/encryption/decryptalltest.php215
-rw-r--r--tests/lib/encryption/decryptalltest.php321
-rw-r--r--tests/lib/files/storage/wrapper/encryption.php2
-rw-r--r--tests/lib/files/stream/encryption.php2
15 files changed, 1482 insertions, 4 deletions
diff --git a/apps/encryption/appinfo/application.php b/apps/encryption/appinfo/application.php
index 75107b2723c..515a408fa2c 100644
--- a/apps/encryption/appinfo/application.php
+++ b/apps/encryption/appinfo/application.php
@@ -30,6 +30,7 @@ use OCA\Encryption\Controller\RecoveryController;
use OCA\Encryption\Controller\SettingsController;
use OCA\Encryption\Controller\StatusController;
use OCA\Encryption\Crypto\Crypt;
+use OCA\Encryption\Crypto\DecryptAll;
use OCA\Encryption\Crypto\EncryptAll;
use OCA\Encryption\Crypto\Encryption;
use OCA\Encryption\HookManager;
@@ -113,7 +114,9 @@ class Application extends \OCP\AppFramework\App {
$container->query('Crypt'),
$container->query('KeyManager'),
$container->query('Util'),
+ $container->query('Session'),
$container->query('EncryptAll'),
+ $container->query('DecryptAll'),
$container->getServer()->getLogger(),
$container->getServer()->getL10N($container->getAppName())
);
@@ -242,6 +245,18 @@ class Application extends \OCP\AppFramework\App {
}
);
+ $container->registerService('DecryptAll',
+ function (IAppContainer $c) {
+ return new DecryptAll(
+ $c->query('Util'),
+ $c->query('KeyManager'),
+ $c->query('Crypt'),
+ $c->query('Session'),
+ new QuestionHelper()
+ );
+ }
+ );
+
}
public function registerSettings() {
diff --git a/apps/encryption/lib/crypto/decryptall.php b/apps/encryption/lib/crypto/decryptall.php
new file mode 100644
index 00000000000..599cd82aa4d
--- /dev/null
+++ b/apps/encryption/lib/crypto/decryptall.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);
+ }
+}
diff --git a/apps/encryption/lib/crypto/encryption.php b/apps/encryption/lib/crypto/encryption.php
index c62afac83c1..fde4a2c4a9c 100644
--- a/apps/encryption/lib/crypto/encryption.php
+++ b/apps/encryption/lib/crypto/encryption.php
@@ -30,6 +30,7 @@ namespace OCA\Encryption\Crypto;
use OC\Encryption\Exceptions\DecryptionFailedException;
use OCA\Encryption\Exceptions\PublicKeyMissingException;
+use OCA\Encryption\Session;
use OCA\Encryption\Util;
use OCP\Encryption\IEncryptionModule;
use OCA\Encryption\KeyManager;
@@ -75,6 +76,9 @@ class Encryption implements IEncryptionModule {
/** @var Util */
private $util;
+ /** @var Session */
+ private $session;
+
/** @var ILogger */
private $logger;
@@ -87,25 +91,34 @@ class Encryption implements IEncryptionModule {
/** @var bool */
private $useMasterPassword;
+ /** @var DecryptAll */
+ private $decryptAll;
+
/**
*
* @param Crypt $crypt
* @param KeyManager $keyManager
* @param Util $util
+ * @param Session $session
* @param EncryptAll $encryptAll
+ * @param DecryptAll $decryptAll
* @param ILogger $logger
* @param IL10N $il10n
*/
public function __construct(Crypt $crypt,
KeyManager $keyManager,
Util $util,
+ Session $session,
EncryptAll $encryptAll,
+ DecryptAll $decryptAll,
ILogger $logger,
IL10N $il10n) {
$this->crypt = $crypt;
$this->keyManager = $keyManager;
$this->util = $util;
+ $this->session = $session;
$this->encryptAll = $encryptAll;
+ $this->decryptAll = $decryptAll;
$this->logger = $logger;
$this->l = $il10n;
$this->useMasterPassword = $util->isMasterKeyEnabled();
@@ -150,7 +163,15 @@ class Encryption implements IEncryptionModule {
$this->isWriteOperation = false;
$this->writeCache = '';
- $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
+ if ($this->session->decryptAllModeActivated()) {
+ $encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
+ $shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid());
+ $this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
+ $shareKey,
+ $this->session->getDecryptAllKey());
+ } else {
+ $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
+ }
if (
$mode === 'w'
@@ -427,6 +448,19 @@ class Encryption implements IEncryptionModule {
}
/**
+ * prepare module to perform decrypt all operation
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @param string $user
+ * @return bool
+ */
+ public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') {
+ return $this->decryptAll->prepare($input, $output, $user);
+ }
+
+
+ /**
* @param string $path
* @return string
*/
diff --git a/apps/encryption/lib/session.php b/apps/encryption/lib/session.php
index c3759c3fc56..1d0c3711487 100644
--- a/apps/encryption/lib/session.php
+++ b/apps/encryption/lib/session.php
@@ -25,6 +25,7 @@
namespace OCA\Encryption;
+use OCA\Encryption\Exceptions\PrivateKeyMissingException;
use \OCP\ISession;
class Session {
@@ -106,6 +107,61 @@ class Session {
$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) {
+ $this->session->set('decryptAll', true);
+ $this->session->set('decryptAllKey', $key);
+ $this->session->set('decryptAllUid', $user);
+ }
+
+ /**
+ * check if we are in decrypt all mode
+ *
+ * @return bool
+ */
+ public function decryptAllModeActivated() {
+ $decryptAll = $this->session->get('decryptAll');
+ return ($decryptAll === true);
+ }
+
+ /**
+ * get uid used for decrypt all operation
+ *
+ * @return string
+ * @throws \Exception
+ */
+ public function getDecryptAllUid() {
+ $uid = $this->session->get('decryptAllUid');
+ if (is_null($uid) && $this->decryptAllModeActivated()) {
+ throw new \Exception('No uid found while in decrypt all mode');
+ } elseif (is_null($uid)) {
+ throw new \Exception('Please activate decrypt all mode first');
+ }
+
+ return $uid;
+ }
+
+ /**
+ * get private key for decrypt all operation
+ *
+ * @return string
+ * @throws PrivateKeyMissingException
+ */
+ public function getDecryptAllKey() {
+ $privateKey = $this->session->get('decryptAllKey');
+ if (is_null($privateKey) && $this->decryptAllModeActivated()) {
+ throw new PrivateKeyMissingException('No private key found while in decrypt all mode');
+ } elseif (is_null($privateKey)) {
+ throw new PrivateKeyMissingException('Please activate decrypt all mode first');
+ }
+
+ return $privateKey;
+ }
/**
* remove keys from session
@@ -114,7 +170,9 @@ class Session {
$this->session->remove('publicSharePrivateKey');
$this->session->remove('privateKey');
$this->session->remove('encryptionInitialized');
-
+ $this->session->remove('decryptAll');
+ $this->session->remove('decryptAllKey');
+ $this->session->remove('decryptAllUid');
}
}
diff --git a/apps/encryption/tests/lib/SessionTest.php b/apps/encryption/tests/lib/SessionTest.php
index e036c439939..0fa48666d70 100644
--- a/apps/encryption/tests/lib/SessionTest.php
+++ b/apps/encryption/tests/lib/SessionTest.php
@@ -56,6 +56,7 @@ class SessionTest extends TestCase {
* @depends testSetAndGetPrivateKey
*/
public function testIsPrivateKeySet() {
+ $this->instance->setPrivateKey('dummyPrivateKey');
$this->assertTrue($this->instance->isPrivateKeySet());
unset(self::$tempStorage['privateKey']);
@@ -65,6 +66,51 @@ class SessionTest extends TestCase {
self::$tempStorage['privateKey'] = 'dummyPrivateKey';
}
+ public function testDecryptAllModeActivated() {
+ $this->instance->prepareDecryptAll('user1', 'usersKey');
+ $this->assertTrue($this->instance->decryptAllModeActivated());
+ $this->assertSame('user1', $this->instance->getDecryptAllUid());
+ $this->assertSame('usersKey', $this->instance->getDecryptAllKey());
+ }
+
+ public function testDecryptAllModeDeactivated() {
+ $this->assertFalse($this->instance->decryptAllModeActivated());
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectExceptionMessage 'Please activate decrypt all mode first'
+ */
+ public function testGetDecryptAllUidException() {
+ $this->instance->getDecryptAllUid();
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectExceptionMessage 'No uid found while in decrypt all mode'
+ */
+ public function testGetDecryptAllUidException2() {
+ $this->instance->prepareDecryptAll(null, 'key');
+ $this->instance->getDecryptAllUid();
+ }
+
+ /**
+ * @expectedException \OCA\Encryption\Exceptions\PrivateKeyMissingException
+ * @expectExceptionMessage 'Please activate decrypt all mode first'
+ */
+ public function testGetDecryptAllKeyException() {
+ $this->instance->getDecryptAllKey();
+ }
+
+ /**
+ * @expectedException \OCA\Encryption\Exceptions\PrivateKeyMissingException
+ * @expectExceptionMessage 'No key found while in decrypt all mode'
+ */
+ public function testGetDecryptAllKeyException2() {
+ $this->instance->prepareDecryptAll('user', null);
+ $this->instance->getDecryptAllKey();
+ }
+
/**
*
*/
@@ -112,6 +158,10 @@ class SessionTest extends TestCase {
*
*/
public function testClearWillRemoveValues() {
+ $this->instance->setPrivateKey('privateKey');
+ $this->instance->setStatus('initStatus');
+ $this->instance->prepareDecryptAll('user', 'key');
+ $this->assertNotEmpty(self::$tempStorage);
$this->instance->clear();
$this->assertEmpty(self::$tempStorage);
}
@@ -138,4 +188,9 @@ class SessionTest extends TestCase {
$this->instance = new Session($this->sessionMock);
}
+
+ protected function tearDown() {
+ self::$tempStorage = [];
+ parent::tearDown();
+ }
}
diff --git a/apps/encryption/tests/lib/crypto/decryptalltest.php b/apps/encryption/tests/lib/crypto/decryptalltest.php
new file mode 100644
index 00000000000..d6a52fe97c0
--- /dev/null
+++ b/apps/encryption/tests/lib/crypto/decryptalltest.php
@@ -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']
+ ];
+ }
+
+}
diff --git a/apps/encryption/tests/lib/crypto/encryptionTest.php b/apps/encryption/tests/lib/crypto/encryptionTest.php
index f58aa5d3ccb..9e0cb2f09d1 100644
--- a/apps/encryption/tests/lib/crypto/encryptionTest.php
+++ b/apps/encryption/tests/lib/crypto/encryptionTest.php
@@ -40,6 +40,12 @@ class EncryptionTest extends TestCase {
private $encryptAllMock;
/** @var \PHPUnit_Framework_MockObject_MockObject */
+ private $decryptAllMock;
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ private $sessionMock;
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
private $cryptMock;
/** @var \PHPUnit_Framework_MockObject_MockObject */
@@ -63,9 +69,15 @@ class EncryptionTest extends TestCase {
$this->keyManagerMock = $this->getMockBuilder('OCA\Encryption\KeyManager')
->disableOriginalConstructor()
->getMock();
+ $this->sessionMock = $this->getMockBuilder('OCA\Encryption\Session')
+ ->disableOriginalConstructor()
+ ->getMock();
$this->encryptAllMock = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll')
->disableOriginalConstructor()
->getMock();
+ $this->decryptAllMock = $this->getMockBuilder('OCA\Encryption\Crypto\DecryptAll')
+ ->disableOriginalConstructor()
+ ->getMock();
$this->loggerMock = $this->getMockBuilder('OCP\ILogger')
->disableOriginalConstructor()
->getMock();
@@ -81,7 +93,9 @@ class EncryptionTest extends TestCase {
$this->cryptMock,
$this->keyManagerMock,
$this->utilMock,
+ $this->sessionMock,
$this->encryptAllMock,
+ $this->decryptAllMock,
$this->loggerMock,
$this->l10nMock
);
@@ -170,6 +184,16 @@ class EncryptionTest extends TestCase {
*/
public function testBegin($mode, $header, $legacyCipher, $defaultCipher, $fileKey, $expected) {
+ $this->sessionMock->expects($this->once())
+ ->method('decryptAllModeActivated')
+ ->willReturn(false);
+
+ $this->sessionMock->expects($this->never())->method('getDecryptAllUid');
+ $this->sessionMock->expects($this->never())->method('getDecryptAllKey');
+ $this->keyManagerMock->expects($this->never())->method('getEncryptedFileKey');
+ $this->keyManagerMock->expects($this->never())->method('getShareKey');
+ $this->cryptMock->expects($this->never())->method('multiKeyDecrypt');
+
$this->cryptMock->expects($this->any())
->method('getCipher')
->willReturn($defaultCipher);
@@ -209,6 +233,49 @@ class EncryptionTest extends TestCase {
);
}
+
+ /**
+ * test begin() if decryptAll mode was activated
+ */
+ public function testBeginDecryptAll() {
+
+ $path = '/user/files/foo.txt';
+ $recoveryKeyId = 'recoveryKeyId';
+ $recoveryShareKey = 'recoveryShareKey';
+ $decryptAllKey = 'decryptAllKey';
+ $fileKey = 'fileKey';
+
+ $this->sessionMock->expects($this->once())
+ ->method('decryptAllModeActivated')
+ ->willReturn(true);
+ $this->sessionMock->expects($this->once())
+ ->method('getDecryptAllUid')
+ ->willReturn($recoveryKeyId);
+ $this->sessionMock->expects($this->once())
+ ->method('getDecryptAllKey')
+ ->willReturn($decryptAllKey);
+
+ $this->keyManagerMock->expects($this->once())
+ ->method('getEncryptedFileKey')
+ ->willReturn('encryptedFileKey');
+ $this->keyManagerMock->expects($this->once())
+ ->method('getShareKey')
+ ->with($path, $recoveryKeyId)
+ ->willReturn($recoveryShareKey);
+ $this->cryptMock->expects($this->once())
+ ->method('multiKeyDecrypt')
+ ->with('encryptedFileKey', $recoveryShareKey, $decryptAllKey)
+ ->willReturn($fileKey);
+
+ $this->keyManagerMock->expects($this->never())->method('getFileKey');
+
+ $this->instance->begin($path, 'user', 'r', [], []);
+
+ $this->assertSame($fileKey,
+ $this->invokePrivate($this->instance, 'fileKey')
+ );
+ }
+
/**
* @dataProvider dataTestUpdate
*
@@ -273,4 +340,15 @@ class EncryptionTest extends TestCase {
public function testDecrypt() {
$this->instance->decrypt('abc');
}
+
+ public function testPrepareDecryptAll() {
+ $input = $this->getMock('Symfony\Component\Console\Input\InputInterface');
+ $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
+
+ $this->decryptAllMock->expects($this->once())->method('prepare')
+ ->with($input, $output, 'user');
+
+ $this->instance->prepareDecryptAll($input, $output, 'user');
+ }
+
}
diff --git a/core/command/encryption/decryptall.php b/core/command/encryption/decryptall.php
new file mode 100644
index 00000000000..374f635725f
--- /dev/null
+++ b/core/command/encryption/decryptall.php
@@ -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;
+ }
+ }
+
+}
diff --git a/core/register_command.php b/core/register_command.php
index d3c04ad0671..114e115c491 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -58,6 +58,13 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) {
$application->add(new OC\Core\Command\Encryption\SetDefaultModule(\OC::$server->getEncryptionManager()));
$application->add(new OC\Core\Command\Encryption\Status(\OC::$server->getEncryptionManager()));
$application->add(new OC\Core\Command\Encryption\EncryptAll(\OC::$server->getEncryptionManager(), \OC::$server->getAppManager(), \OC::$server->getConfig(), new \Symfony\Component\Console\Helper\QuestionHelper()));
+ $application->add(new OC\Core\Command\Encryption\DecryptAll(
+ \OC::$server->getEncryptionManager(),
+ \OC::$server->getAppManager(),
+ \OC::$server->getConfig(),
+ new \OC\Encryption\DecryptAll(\OC::$server->getEncryptionManager(), \OC::$server->getUserManager(), new \OC\Files\View()),
+ new \Symfony\Component\Console\Helper\QuestionHelper())
+ );
$application->add(new OC\Core\Command\Log\Manage(\OC::$server->getConfig()));
$application->add(new OC\Core\Command\Log\OwnCloud(\OC::$server->getConfig()));
diff --git a/lib/private/encryption/decryptall.php b/lib/private/encryption/decryptall.php
new file mode 100644
index 00000000000..e59be17886d
--- /dev/null
+++ b/lib/private/encryption/decryptall.php
@@ -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);
+ }
+
+}
diff --git a/lib/public/encryption/iencryptionmodule.php b/lib/public/encryption/iencryptionmodule.php
index 0fa0dc570db..66cf1a80e0d 100644
--- a/lib/public/encryption/iencryptionmodule.php
+++ b/lib/public/encryption/iencryptionmodule.php
@@ -145,4 +145,15 @@ interface IEncryptionModule {
*/
public function encryptAll(InputInterface $input, OutputInterface $output);
+ /**
+ * prepare encryption module to decrypt all files
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output write some status information to the terminal during encryption
+ * @param $user (optional) for which the files should be decrypted, default = all users
+ * @return bool return false on failure or if it isn't supported by the module
+ * @since 8.2.0
+ */
+ public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '');
+
}
diff --git a/tests/core/command/encryption/decryptalltest.php b/tests/core/command/encryption/decryptalltest.php
new file mode 100644
index 00000000000..b7fd630e9b0
--- /dev/null
+++ b/tests/core/command/encryption/decryptalltest.php
@@ -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]);
+ }
+
+}
diff --git a/tests/lib/encryption/decryptalltest.php b/tests/lib/encryption/decryptalltest.php
new file mode 100644
index 00000000000..eb3bc721f86
--- /dev/null
+++ b/tests/lib/encryption/decryptalltest.php
@@ -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])
+ );
+ }
+
+}
diff --git a/tests/lib/files/storage/wrapper/encryption.php b/tests/lib/files/storage/wrapper/encryption.php
index 36a5b288c64..44e910b901f 100644
--- a/tests/lib/files/storage/wrapper/encryption.php
+++ b/tests/lib/files/storage/wrapper/encryption.php
@@ -194,7 +194,7 @@ class Encryption extends \Test\Files\Storage\Storage {
protected function buildMockModule() {
$this->encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule')
->disableOriginalConstructor()
- ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll'])
+ ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll'])
->getMock();
$this->encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE');
diff --git a/tests/lib/files/stream/encryption.php b/tests/lib/files/stream/encryption.php
index ed3b5b1b156..f9d8f076b63 100644
--- a/tests/lib/files/stream/encryption.php
+++ b/tests/lib/files/stream/encryption.php
@@ -305,7 +305,7 @@ class Encryption extends \Test\TestCase {
protected function buildMockModule() {
$encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule')
->disableOriginalConstructor()
- ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll'])
+ ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll'])
->getMock();
$encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE');