Merge pull request #18423 from owncloud/occ_encrypt_all

occ command line tool to encrypt all files
This commit is contained in:
Björn Schießle 2015-08-28 20:44:55 +02:00
commit 6e210d960c
15 changed files with 1101 additions and 84 deletions

View File

@ -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\EncryptAll;
use OCA\Encryption\Crypto\Encryption;
use OCA\Encryption\HookManager;
use OCA\Encryption\Hooks\UserHooks;
@ -42,6 +43,7 @@ use OCP\App;
use OCP\AppFramework\IAppContainer;
use OCP\Encryption\IManager;
use OCP\IConfig;
use Symfony\Component\Console\Helper\QuestionHelper;
class Application extends \OCP\AppFramework\App {
@ -111,6 +113,7 @@ class Application extends \OCP\AppFramework\App {
$container->query('Crypt'),
$container->query('KeyManager'),
$container->query('Util'),
$container->query('EncryptAll'),
$container->getServer()->getLogger(),
$container->getServer()->getL10N($container->getAppName())
);
@ -221,6 +224,23 @@ class Application extends \OCP\AppFramework\App {
$server->getUserManager());
});
$container->registerService('EncryptAll',
function (IAppContainer $c) {
$server = $c->getServer();
return new EncryptAll(
$c->query('UserSetup'),
$c->getServer()->getUserManager(),
new View(),
$c->query('KeyManager'),
$server->getConfig(),
$server->getMailer(),
$server->getL10N('encryption'),
new QuestionHelper(),
$server->getSecureRandom()
);
}
);
}
public function registerSettings() {

View File

@ -0,0 +1,424 @@
<?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 OC\Encryption\Exceptions\DecryptionFailedException;
use OC\Files\View;
use OCA\Encryption\KeyManager;
use OCA\Encryption\Users\Setup;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IUserManager;
use OCP\Mail\IMailer;
use OCP\Security\ISecureRandom;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class EncryptAll {
/** @var Setup */
protected $userSetup;
/** @var IUserManager */
protected $userManager;
/** @var View */
protected $rootView;
/** @var KeyManager */
protected $keyManager;
/** @var array */
protected $userPasswords;
/** @var IConfig */
protected $config;
/** @var IMailer */
protected $mailer;
/** @var IL10N */
protected $l;
/** @var QuestionHelper */
protected $questionHelper;
/** @var OutputInterface */
protected $output;
/** @var InputInterface */
protected $input;
/** @var ISecureRandom */
protected $secureRandom;
/**
* @param Setup $userSetup
* @param IUserManager $userManager
* @param View $rootView
* @param KeyManager $keyManager
* @param IConfig $config
* @param IMailer $mailer
* @param IL10N $l
* @param QuestionHelper $questionHelper
* @param ISecureRandom $secureRandom
*/
public function __construct(
Setup $userSetup,
IUserManager $userManager,
View $rootView,
KeyManager $keyManager,
IConfig $config,
IMailer $mailer,
IL10N $l,
QuestionHelper $questionHelper,
ISecureRandom $secureRandom
) {
$this->userSetup = $userSetup;
$this->userManager = $userManager;
$this->rootView = $rootView;
$this->keyManager = $keyManager;
$this->config = $config;
$this->mailer = $mailer;
$this->l = $l;
$this->questionHelper = $questionHelper;
$this->secureRandom = $secureRandom;
// store one time passwords for the users
$this->userPasswords = array();
}
/**
* start to encrypt all files
*
* @param InputInterface $input
* @param OutputInterface $output
*/
public function encryptAll(InputInterface $input, OutputInterface $output) {
$this->input = $input;
$this->output = $output;
$headline = 'Encrypt all files with the ' . Encryption::DISPLAY_NAME;
$this->output->writeln("\n");
$this->output->writeln($headline);
$this->output->writeln(str_pad('', strlen($headline), '='));
//create private/public keys for each user and store the private key password
$this->output->writeln("\n");
$this->output->writeln('Create key-pair for every user');
$this->output->writeln('------------------------------');
$this->output->writeln('');
$this->output->writeln('This module will encrypt all files in the users files folder initially.');
$this->output->writeln('Already existing versions and files in the trash bin will not be encrypted.');
$this->output->writeln('');
$this->createKeyPairs();
//setup users file system and encrypt all files one by one (take should encrypt setting of storage into account)
$this->output->writeln("\n");
$this->output->writeln('Start to encrypt users files');
$this->output->writeln('----------------------------');
$this->output->writeln('');
$this->encryptAllUsersFiles();
//send-out or display password list and write it to a file
$this->output->writeln("\n");
$this->output->writeln('Generated encryption key passwords');
$this->output->writeln('----------------------------------');
$this->output->writeln('');
$this->outputPasswords();
$this->output->writeln("\n");
}
/**
* create key-pair for every user
*/
protected function createKeyPairs() {
$this->output->writeln("\n");
$progress = new ProgressBar($this->output);
$progress->setFormat(" %message% \n [%bar%]");
$progress->start();
foreach($this->userManager->getBackends() as $backend) {
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers('', $limit, $offset);
foreach ($users as $user) {
if ($this->keyManager->userHasKeys($user) === false) {
$progress->setMessage('Create key-pair for ' . $user);
$progress->advance();
$this->setupUserFS($user);
$password = $this->generateOneTimePassword($user);
$this->userSetup->setupUser($user, $password);
} else {
// users which already have a key-pair will be stored with a
// empty password and filtered out later
$this->userPasswords[$user] = '';
}
}
$offset += $limit;
} while(count($users) >= $limit);
}
$progress->setMessage('Key-pair created for all users');
$progress->finish();
}
/**
* iterate over all user and encrypt their files
*/
protected function encryptAllUsersFiles() {
$this->output->writeln("\n");
$progress = new ProgressBar($this->output);
$progress->setFormat(" %message% \n [%bar%]");
$progress->start();
$numberOfUsers = count($this->userPasswords);
$userNo = 1;
foreach ($this->userPasswords as $uid => $password) {
$userCount = "$uid ($userNo of $numberOfUsers)";
$this->encryptUsersFiles($uid, $progress, $userCount);
$userNo++;
}
$progress->setMessage("all files encrypted");
$progress->finish();
}
/**
* encrypt files from the given user
*
* @param string $uid
* @param ProgressBar $progress
* @param string $userCount
*/
protected function encryptUsersFiles($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 {
$progress->setMessage("encrypt files for user $userCount: $path");
$progress->advance();
if($this->encryptFile($path) === false) {
$progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
$progress->advance();
}
}
}
}
}
/**
* encrypt file
*
* @param string $path
* @return bool
*/
protected function encryptFile($path) {
$source = $path;
$target = $path . '.encrypted.' . time();
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;
}
/**
* output one-time encryption passwords
*/
protected function outputPasswords() {
$table = new Table($this->output);
$table->setHeaders(array('Username', 'Private key password'));
//create rows
$newPasswords = array();
$unchangedPasswords = array();
foreach ($this->userPasswords as $uid => $password) {
if (empty($password)) {
$unchangedPasswords[] = $uid;
} else {
$newPasswords[] = [$uid, $password];
}
}
$table->setRows($newPasswords);
$table->render();
if (!empty($unchangedPasswords)) {
$this->output->writeln("\nThe following users already had a key-pair which was reused without setting a new password:\n");
foreach ($unchangedPasswords as $uid) {
$this->output->writeln(" $uid");
}
}
$this->writePasswordsToFile($newPasswords);
$this->output->writeln('');
$question = new ConfirmationQuestion('Do you want to send the passwords directly to the users by mail? (y/n) ', false);
if ($this->questionHelper->ask($this->input, $this->output, $question)) {
$this->sendPasswordsByMail();
}
}
/**
* write one-time encryption passwords to a csv file
*
* @param array $passwords
*/
protected function writePasswordsToFile(array $passwords) {
$fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w');
foreach ($passwords as $pwd) {
fputcsv($fp, $pwd);
}
fclose($fp);
$this->output->writeln("\n");
$this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv');
$this->output->writeln('');
$this->output->writeln('Each of these users need to login to the web interface, go to the');
$this->output->writeln('personal settings section "ownCloud basic encryption module" and');
$this->output->writeln('update the private key password to match the login password again by');
$this->output->writeln('entering the one-time password into the "old log-in password" field');
$this->output->writeln('and their current login password');
}
/**
* setup user file system
*
* @param string $uid
*/
protected function setupUserFS($uid) {
\OC_Util::tearDownFS();
\OC_Util::setupFS($uid);
}
/**
* generate one time password for the user and store it in a array
*
* @param string $uid
* @return string password
*/
protected function generateOneTimePassword($uid) {
$password = $this->secureRandom->getMediumStrengthGenerator()->generate(8);
$this->userPasswords[$uid] = $password;
return $password;
}
/**
* send encryption key passwords to the users by mail
*/
protected function sendPasswordsByMail() {
$noMail = [];
$this->output->writeln('');
$progress = new ProgressBar($this->output, count($this->userPasswords));
$progress->start();
foreach ($this->userPasswords as $recipient => $password) {
$progress->advance();
if (!empty($password)) {
$recipientDisplayName = $this->userManager->get($recipient)->getDisplayName();
$to = $this->config->getUserValue($recipient, 'settings', 'email', '');
if ($to === '') {
$noMail[] = $recipient;
continue;
}
$subject = (string)$this->l->t('one-time password for server-side-encryption');
list($htmlBody, $textBody) = $this->createMailBody($password);
// send it out now
try {
$message = $this->mailer->createMessage();
$message->setSubject($subject);
$message->setTo([$to => $recipientDisplayName]);
$message->setHtmlBody($htmlBody);
$message->setPlainBody($textBody);
$message->setFrom([
\OCP\Util::getDefaultEmailAddress('admin-noreply')
]);
$this->mailer->send($message);
} catch (\Exception $e) {
$noMail[] = $recipient;
}
}
}
$progress->finish();
if (empty($noMail)) {
$this->output->writeln("\n\nPassword successfully send to all users");
} else {
$table = new Table($this->output);
$table->setHeaders(array('Username', 'Private key password'));
$this->output->writeln("\n\nCould not send password to following users:\n");
$rows = [];
foreach ($noMail as $uid) {
$rows[] = [$uid, $this->userPasswords[$uid]];
}
$table->setRows($rows);
$table->render();
}
}
/**
* create mail body for plain text and html mail
*
* @param string $password one-time encryption password
* @return array an array of the html mail body and the plain text mail body
*/
protected function createMailBody($password) {
$html = new \OC_Template("encryption", "mail", "");
$html->assign ('password', $password);
$htmlMail = $html->fetchPage();
$plainText = new \OC_Template("encryption", "altmail", "");
$plainText->assign ('password', $password);
$plainTextMail = $plainText->fetchPage();
return [$htmlMail, $plainTextMail];
}
}

View File

@ -35,6 +35,8 @@ use OCP\Encryption\IEncryptionModule;
use OCA\Encryption\KeyManager;
use OCP\IL10N;
use OCP\ILogger;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Encryption implements IEncryptionModule {
@ -79,22 +81,28 @@ class Encryption implements IEncryptionModule {
/** @var IL10N */
private $l;
/** @var EncryptAll */
private $encryptAll;
/**
*
* @param Crypt $crypt
* @param KeyManager $keyManager
* @param Util $util
* @param EncryptAll $encryptAll
* @param ILogger $logger
* @param IL10N $il10n
*/
public function __construct(Crypt $crypt,
KeyManager $keyManager,
Util $util,
EncryptAll $encryptAll,
ILogger $logger,
IL10N $il10n) {
$this->crypt = $crypt;
$this->keyManager = $keyManager;
$this->util = $util;
$this->encryptAll = $encryptAll;
$this->logger = $logger;
$this->l = $il10n;
}
@ -397,6 +405,17 @@ class Encryption implements IEncryptionModule {
return true;
}
/**
* Initial encryption of all files
*
* @param InputInterface $input
* @param OutputInterface $output write some status information to the terminal during encryption
* @return bool
*/
public function encryptAll(InputInterface $input, OutputInterface $output) {
return $this->encryptAll->encryptAll($input, $output);
}
/**
* @param string $path
* @return string

View File

@ -76,6 +76,8 @@ class Setup {
}
/**
* check if user has a key pair, if not we create one
*
* @param string $uid userid
* @param string $password user password
* @return bool

View File

@ -0,0 +1,16 @@
<?php
/** @var OC_Theme $theme */
/** @var array $_ */
print_unescaped($l->t("Hey there,\n\nthe admin enabled server-side-encryption. Your files were encrypted using the password '%s'.\n\nPlease login to the web interface, go to the section 'ownCloud basic encryption module' of your personal settings and update your encryption password by entering this password into the 'old log-in password' field and your current login-password.\n\n", array($_['password'])));
if ( isset($_['expiration']) ) {
print_unescaped($l->t("The share will expire on %s.", array($_['expiration'])));
print_unescaped("\n\n");
}
// TRANSLATORS term at the end of a mail
p($l->t("Cheers!"));
?>
--
<?php p($theme->getName() . ' - ' . $theme->getSlogan()); ?>
<?php print_unescaped("\n".$theme->getBaseUrl());

View File

@ -0,0 +1,39 @@
<?php
/** @var OC_Theme $theme */
/** @var array $_ */
?>
<table cellspacing="0" cellpadding="0" border="0" width="100%">
<tr><td>
<table cellspacing="0" cellpadding="0" border="0" width="600px">
<tr>
<td bgcolor="<?php p($theme->getMailHeaderColor());?>" width="20px">&nbsp;</td>
<td bgcolor="<?php p($theme->getMailHeaderColor());?>">
<img src="<?php p(\OC::$server->getURLGenerator()->getAbsoluteURL(image_path('', 'logo-mail.gif'))); ?>" alt="<?php p($theme->getName()); ?>"/>
</td>
</tr>
<tr><td colspan="2">&nbsp;</td></tr>
<tr>
<td width="20px">&nbsp;</td>
<td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">
<?php
print_unescaped($l->t('Hey there,<br><br>the admin enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>Please login to the web interface, go to the section "ownCloud basic encryption module" of your personal settings and update your encryption password by entering this password into the "old log-in password" field and your current login-password.<br><br>', array($_['password'])));
// TRANSLATORS term at the end of a mail
p($l->t('Cheers!'));
?>
</td>
</tr>
<tr><td colspan="2">&nbsp;</td></tr>
<tr>
<td width="20px">&nbsp;</td>
<td style="font-weight:normal; font-size:0.8em; line-height:1.2em; font-family:verdana,'arial',sans;">--<br>
<?php p($theme->getName()); ?> -
<?php p($theme->getSlogan()); ?>
<br><a href="<?php p($theme->getBaseUrl()); ?>"><?php p($theme->getBaseUrl());?></a>
</td>
</tr>
<tr>
<td colspan="2">&nbsp;</td>
</tr>
</table>
</td></tr>
</table>

View File

@ -0,0 +1,291 @@
<?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\EncryptAll;
use Test\TestCase;
class EncryptAllTest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCA\Encryption\KeyManager */
protected $keyManager;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IUserManager */
protected $userManager;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCA\Encryption\Users\Setup */
protected $setupUser;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\View */
protected $view;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IConfig */
protected $config;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\Mail\IMailer */
protected $mailer;
/** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IL10N */
protected $l;
/** @var \PHPUnit_Framework_MockObject_MockObject | \Symfony\Component\Console\Helper\QuestionHelper */
protected $questionHelper;
/** @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 \PHPUnit_Framework_MockObject_MockObject | \OCP\Security\ISecureRandom */
protected $secureRandom;
/** @var EncryptAll */
protected $encryptAll;
function setUp() {
parent::setUp();
$this->setupUser = $this->getMockBuilder('OCA\Encryption\Users\Setup')
->disableOriginalConstructor()->getMock();
$this->keyManager = $this->getMockBuilder('OCA\Encryption\KeyManager')
->disableOriginalConstructor()->getMock();
$this->userManager = $this->getMockBuilder('OCP\IUserManager')
->disableOriginalConstructor()->getMock();
$this->view = $this->getMockBuilder('OC\Files\View')
->disableOriginalConstructor()->getMock();
$this->config = $this->getMockBuilder('OCP\IConfig')
->disableOriginalConstructor()->getMock();
$this->mailer = $this->getMockBuilder('OCP\Mail\IMailer')
->disableOriginalConstructor()->getMock();
$this->l = $this->getMockBuilder('OCP\IL10N')
->disableOriginalConstructor()->getMock();
$this->questionHelper = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper')
->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->userManager->expects($this->any())->method('getBackends')->willReturn([$this->userInterface]);
$this->userInterface->expects($this->any())->method('getUsers')->willReturn(['user1', 'user2']);
$this->secureRandom = $this->getMockBuilder('OCP\Security\ISecureRandom')->disableOriginalConstructor()->getMock();
$this->secureRandom->expects($this->any())->method('getMediumStrengthGenerator')->willReturn($this->secureRandom);
$this->secureRandom->expects($this->any())->method('getLowStrengthGenerator')->willReturn($this->secureRandom);
$this->secureRandom->expects($this->any())->method('generate')->willReturn('12345678');
$this->encryptAll = new EncryptAll(
$this->setupUser,
$this->userManager,
$this->view,
$this->keyManager,
$this->config,
$this->mailer,
$this->l,
$this->questionHelper,
$this->secureRandom
);
}
public function testEncryptAll() {
/** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */
$encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll')
->setConstructorArgs(
[
$this->setupUser,
$this->userManager,
$this->view,
$this->keyManager,
$this->config,
$this->mailer,
$this->l,
$this->questionHelper,
$this->secureRandom
]
)
->setMethods(['createKeyPairs', 'encryptAllUsersFiles', 'outputPasswords'])
->getMock();
$encryptAll->expects($this->at(0))->method('createKeyPairs')->with();
$encryptAll->expects($this->at(1))->method('encryptAllUsersFiles')->with();
$encryptAll->expects($this->at(2))->method('outputPasswords')->with();
$encryptAll->encryptAll($this->inputInterface, $this->outputInterface);
}
public function testCreateKeyPairs() {
/** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */
$encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll')
->setConstructorArgs(
[
$this->setupUser,
$this->userManager,
$this->view,
$this->keyManager,
$this->config,
$this->mailer,
$this->l,
$this->questionHelper,
$this->secureRandom
]
)
->setMethods(['setupUserFS', 'generateOneTimePassword'])
->getMock();
// set protected property $output
$this->invokePrivate($encryptAll, 'output', [$this->outputInterface]);
$this->keyManager->expects($this->exactly(2))->method('userHasKeys')
->willReturnCallback(
function ($user) {
if ($user === 'user1') {
return false;
}
return true;
}
);
$encryptAll->expects($this->once())->method('setupUserFS')->with('user1');
$encryptAll->expects($this->once())->method('generateOneTimePassword')->with('user1')->willReturn('password');
$this->setupUser->expects($this->once())->method('setupUser')->with('user1', 'password');
$this->invokePrivate($encryptAll, 'createKeyPairs');
$userPasswords = $this->invokePrivate($encryptAll, 'userPasswords');
// we only expect the skipped user, because generateOneTimePassword which
// would set the user with the new password was mocked.
// This method will be tested separately
$this->assertSame(1, count($userPasswords));
$this->assertSame('', $userPasswords['user2']);
}
public function testEncryptAllUsersFiles() {
/** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */
$encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll')
->setConstructorArgs(
[
$this->setupUser,
$this->userManager,
$this->view,
$this->keyManager,
$this->config,
$this->mailer,
$this->l,
$this->questionHelper,
$this->secureRandom
]
)
->setMethods(['encryptUsersFiles'])
->getMock();
// set protected property $output
$this->invokePrivate($encryptAll, 'output', [$this->outputInterface]);
$this->invokePrivate($encryptAll, 'userPasswords', [['user1' => 'pwd1', 'user2' => 'pwd2']]);
$encryptAll->expects($this->at(0))->method('encryptUsersFiles')->with('user1');
$encryptAll->expects($this->at(1))->method('encryptUsersFiles')->with('user2');
$this->invokePrivate($encryptAll, 'encryptAllUsersFiles');
}
public function testEncryptUsersFiles() {
/** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */
$encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll')
->setConstructorArgs(
[
$this->setupUser,
$this->userManager,
$this->view,
$this->keyManager,
$this->config,
$this->mailer,
$this->l,
$this->questionHelper,
$this->secureRandom
]
)
->setMethods(['encryptFile'])
->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;
}
);
$encryptAll->expects($this->at(0))->method('encryptFile')->with('/user1/files/bar');
$encryptAll->expects($this->at(1))->method('encryptFile')->with('/user1/files/foo/subfile');
$progressBar = $this->getMockBuilder('Symfony\Component\Console\Helper\ProgressBar')
->disableOriginalConstructor()->getMock();
$this->invokePrivate($encryptAll, 'encryptUsersFiles', ['user1', $progressBar, '']);
}
public function testGenerateOneTimePassword() {
$password = $this->invokePrivate($this->encryptAll, 'generateOneTimePassword', ['user1']);
$this->assertTrue(is_string($password));
$this->assertSame(8, strlen($password));
$userPasswords = $this->invokePrivate($this->encryptAll, 'userPasswords');
$this->assertSame(1, count($userPasswords));
$this->assertSame($password, $userPasswords['user1']);
}
}

View File

@ -36,6 +36,9 @@ class EncryptionTest extends TestCase {
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $keyManagerMock;
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $encryptAllMock;
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $cryptMock;
@ -60,6 +63,9 @@ class EncryptionTest extends TestCase {
$this->keyManagerMock = $this->getMockBuilder('OCA\Encryption\KeyManager')
->disableOriginalConstructor()
->getMock();
$this->encryptAllMock = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll')
->disableOriginalConstructor()
->getMock();
$this->loggerMock = $this->getMockBuilder('OCP\ILogger')
->disableOriginalConstructor()
->getMock();
@ -75,6 +81,7 @@ class EncryptionTest extends TestCase {
$this->cryptMock,
$this->keyManagerMock,
$this->utilMock,
$this->encryptAllMock,
$this->loggerMock,
$this->l10nMock
);

View File

@ -0,0 +1,114 @@
<?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\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class EncryptAll 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;
/**
* @param IManager $encryptionManager
* @param IAppManager $appManager
* @param IConfig $config
* @param QuestionHelper $questionHelper
*/
public function __construct(
IManager $encryptionManager,
IAppManager $appManager,
IConfig $config,
QuestionHelper $questionHelper
) {
parent::__construct();
$this->appManager = $appManager;
$this->encryptionManager = $encryptionManager;
$this->config = $config;
$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:encrypt_all');
$this->setDescription(
'This will encrypt all files for all users. '
. 'Please make sure that no user access his files during this process!'
);
}
protected function execute(InputInterface $input, OutputInterface $output) {
if ($this->encryptionManager->isEnabled() === false) {
throw new \Exception('Server side encryption is not enabled');
}
$output->writeln("\n");
$output->writeln('You are about to start to encrypt all files stored in your ownCloud.');
$output->writeln('It will depend on the encryption module you use which files get encrypted.');
$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)) {
$defaultModule = $this->encryptionManager->getEncryptionModule();
$defaultModule->encryptAll($input, $output);
} else {
$output->writeln('aborted');
}
}
}

View File

@ -57,6 +57,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) {
$application->add(new OC\Core\Command\Encryption\ListModules(\OC::$server->getEncryptionManager()));
$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\Log\Manage(\OC::$server->getConfig()));
$application->add(new OC\Core\Command\Log\OwnCloud(\OC::$server->getConfig()));

View File

@ -67,7 +67,6 @@ class Encryption extends Wrapper {
/** @var IMountPoint */
private $mount;
/** @var IStorage */
private $keyStorage;
@ -300,33 +299,15 @@ class Encryption extends Wrapper {
public function copy($path1, $path2) {
$source = $this->getFullPath($path1);
$target = $this->getFullPath($path2);
if ($this->util->isExcluded($source)) {
return $this->storage->copy($path1, $path2);
}
$result = $this->storage->copy($path1, $path2);
if ($result && $this->encryptionManager->isEnabled()) {
$keysCopied = $this->copyKeys($source, $target);
if ($keysCopied &&
dirname($source) !== dirname($target) &&
$this->util->isFile($target)
) {
$this->update->update($target);
}
$data = $this->getMetaData($path1);
if (isset($data['encrypted'])) {
$this->getCache()->put($path2, ['encrypted' => $data['encrypted']]);
}
if (isset($data['size'])) {
$this->updateUnencryptedSize($target, $data['size']);
}
}
// need to stream copy file by file in case we copy between a encrypted
// and a unencrypted storage
$this->unlink($path2);
$result = $this->copyFromStorage($this, $path1, $path2);
return $result;
}
@ -511,12 +492,17 @@ class Encryption extends Wrapper {
}
}
} else {
$source = $sourceStorage->fopen($sourceInternalPath, 'r');
$target = $this->fopen($targetInternalPath, 'w');
list(, $result) = \OC_Helper::streamCopy($source, $target);
fclose($source);
fclose($target);
try {
$source = $sourceStorage->fopen($sourceInternalPath, 'r');
$target = $this->fopen($targetInternalPath, 'w');
list(, $result) = \OC_Helper::streamCopy($source, $target);
fclose($source);
fclose($target);
} catch (\Exception $e) {
fclose($source);
fclose($target);
throw $e;
}
if($result) {
if ($preserveMtime) {
$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));

View File

@ -23,6 +23,8 @@
*/
namespace OCP\Encryption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Interface IEncryptionModule
@ -134,4 +136,14 @@ interface IEncryptionModule {
*/
public function isReadable($path, $uid);
/**
* Initial encryption of all files
*
* @param InputInterface $input
* @param OutputInterface $output write some status information to the terminal during encryption
* @return bool
* @since 8.2.0
*/
public function encryptAll(InputInterface $input, OutputInterface $output);
}

View File

@ -0,0 +1,131 @@
<?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\EncryptAll;
use Test\TestCase;
class EncryptAllTest 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 | \OCP\Encryption\IEncryptionModule */
protected $encryptionModule;
/** @var EncryptAll */
protected $command;
protected 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->encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule')
->disableOriginalConstructor()
->getMock();
$this->questionHelper = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper')
->disableOriginalConstructor()
->getMock();
$this->consoleInput = $this->getMock('Symfony\Component\Console\Input\InputInterface');
$this->consoleOutput = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
}
public function testEncryptAll() {
// trash bin needs to be disabled in order to avoid adding dummy files to the users
// trash bin which gets deleted during the encryption process
$this->appManager->expects($this->once())->method('disableApp')->with('files_trashbin');
// enable single user mode to avoid that other user login during encryption
// destructor should disable the single user mode again
$this->config->expects($this->once())->method('getSystemValue')->with('singleUser', false)->willReturn(false);
$this->config->expects($this->at(1))->method('setSystemValue')->with('singleUser', true);
$this->config->expects($this->at(2))->method('setSystemValue')->with('singleUser', false);
new EncryptAll($this->encryptionManager, $this->appManager, $this->config, $this->questionHelper);
}
/**
* @dataProvider dataTestExecute
*/
public function testExecute($answer, $askResult) {
$command = new EncryptAll($this->encryptionManager, $this->appManager, $this->config, $this->questionHelper);
$this->encryptionManager->expects($this->once())->method('isEnabled')->willReturn(true);
$this->questionHelper->expects($this->once())->method('ask')->willReturn($askResult);
if ($answer === 'Y' || $answer === 'y') {
$this->encryptionManager->expects($this->once())
->method('getEncryptionModule')->willReturn($this->encryptionModule);
$this->encryptionModule->expects($this->once())
->method('encryptAll')->with($this->consoleInput, $this->consoleOutput);
} else {
$this->encryptionManager->expects($this->never())->method('getEncryptionModule');
$this->encryptionModule->expects($this->never())->method('encryptAll');
}
$this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput]);
}
public function dataTestExecute() {
return [
['y', true], ['Y', true], ['n', false], ['N', false], ['', false]
];
}
/**
* @expectedException \Exception
*/
public function testExecuteException() {
$command = new EncryptAll($this->encryptionManager, $this->appManager, $this->config, $this->questionHelper);
$this->encryptionManager->expects($this->once())->method('isEnabled')->willReturn(false);
$this->encryptionManager->expects($this->never())->method('getEncryptionModule');
$this->encryptionModule->expects($this->never())->method('encryptAll');
$this->invokePrivate($command, 'execute', [$this->consoleInput, $this->consoleOutput]);
}
}

View File

@ -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'])
->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll'])
->getMock();
$this->encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE');
@ -241,59 +241,14 @@ class Encryption extends \Test\Files\Storage\Storage {
$this->instance->rename($source, $target);
}
/**
* @dataProvider dataTestCopyAndRename
*
* @param string $source
* @param string $target
* @param $encryptionEnabled
* @param boolean $copyKeysReturn
* @param boolean $shouldUpdate
*/
public function testCopyEncryption($source,
$target,
$encryptionEnabled,
$copyKeysReturn,
$shouldUpdate) {
if ($encryptionEnabled) {
$this->keyStore
->expects($this->once())
->method('copyKeys')
->willReturn($copyKeysReturn);
$this->cache->expects($this->atLeastOnce())
->method('put')
->willReturnCallback(function($path, $data) {
$this->assertArrayHasKey('encrypted', $data);
$this->assertTrue($data['encrypted']);
});
} else {
$this->cache->expects($this->never())->method('put');
$this->keyStore->expects($this->never())->method('copyKeys');
}
$this->util->expects($this->any())
->method('isFile')->willReturn(true);
$this->util->expects($this->any())
->method('isExcluded')->willReturn(false);
$this->encryptionManager->expects($this->once())
->method('isEnabled')->willReturn($encryptionEnabled);
if ($shouldUpdate) {
$this->update->expects($this->once())
->method('update');
} else {
$this->update->expects($this->never())
->method('update');
}
$this->instance->mkdir($source);
$this->instance->mkdir(dirname($target));
$this->instance->copy($source, $target);
if ($encryptionEnabled) {
$this->assertSame($this->dummySize,
$this->instance->filesize($target)
);
}
public function testCopyEncryption() {
$this->instance->file_put_contents('source.txt', 'bar');
$this->instance->copy('source.txt', 'target.txt');
$this->assertSame('bar', $this->instance->file_get_contents('target.txt'));
$targetMeta = $this->instance->getMetaData('target.txt');
$sourceMeta = $this->instance->getMetaData('source.txt');
$this->assertSame($sourceMeta['encrypted'], $targetMeta['encrypted']);
$this->assertSame($sourceMeta['size'], $targetMeta['size']);
}
/**

View File

@ -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'])
->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll'])
->getMock();
$encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE');