Browse Source

add migration script from old encryption to new one

remotes/origin/poc-doctrine-migrations
Bjoern Schiessle 11 years ago
parent
commit
e3d77c4b01
  1. 8
      apps/encryption/appinfo/app.php
  2. 13
      apps/encryption/appinfo/application.php
  3. 2
      apps/encryption/appinfo/info.xml
  4. 15
      apps/encryption/appinfo/register_command.php
  5. 105
      apps/encryption/command/migratekeys.php
  6. 319
      apps/encryption/lib/migration.php
  7. 356
      apps/encryption/tests/lib/MigrationTest.php
  8. 42
      lib/private/encryption/manager.php
  9. 10
      lib/private/encryption/util.php
  10. 11
      lib/private/files/storage/wrapper/encryption.php
  11. 2
      lib/private/server.php
  12. 1
      settings/admin.php
  13. 9
      settings/templates/admin.php
  14. 95
      tests/lib/encryption/managertest.php

8
apps/encryption/appinfo/app.php

@ -24,6 +24,8 @@
namespace OCA\Encryption\AppInfo;
$app = new Application();
$app->registerEncryptionModule();
$app->registerHooks();
$app->registerSettings();
if (\OC::$server->getEncryptionManager()->isReady()) {
$app->registerEncryptionModule();
$app->registerHooks();
$app->registerSettings();
}

13
apps/encryption/appinfo/application.php

@ -40,13 +40,10 @@ use OCP\IConfig;
class Application extends \OCP\AppFramework\App {
/**
* @var IManager
*/
/** @var IManager */
private $encryptionManager;
/**
* @var IConfig
*/
/** @var IConfig */
private $config;
/**
@ -59,6 +56,10 @@ class Application extends \OCP\AppFramework\App {
$this->registerServices();
}
/**
* register hooks
*/
public function registerHooks() {
if (!$this->config->getSystemValue('maintenance', false)) {

2
apps/encryption/appinfo/info.xml

@ -16,7 +16,7 @@
based on AES 128 or 256 bit keys. More information is available in
the Encryption documentation
</description>
<name>Encryption</name>
<name>ownCloud Default Encryption Module</name>
<license>AGPL</license>
<author>Bjoern Schiessle, Clark Tomlinson</author>
<requiremin>8</requiremin>

15
apps/encryption/appinfo/register_command.php

@ -0,0 +1,15 @@
<?php
/**
* Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
use OCA\Encryption\Command\MigrateKeys;
$userManager = OC::$server->getUserManager();
$view = new \OC\Files\View();
$config = \OC::$server->getConfig();
$connection = \OC::$server->getDatabaseConnection();
$application->add(new MigrateKeys($userManager, $view, $connection, $config));

105
apps/encryption/command/migratekeys.php

@ -0,0 +1,105 @@
<?php
/**
* Copyright (c) 2015 Thomas Müller <thomas.mueller@tmit.eu>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Encryption\Command;
use OC\DB\Connection;
use OC\Files\View;
use OC\User\Manager;
use OCA\Encryption\Migration;
use OCP\IConfig;
use OCP\IUserBackend;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MigrateKeys extends Command {
/** @var \OC\User\Manager */
private $userManager;
/** @var View */
private $view;
/** @var \OC\DB\Connection */
private $connection;
/** @var IConfig */
private $config;
/**
* @param Manager $userManager
* @param View $view
* @param Connection $connection
* @param IConfig $config
*/
public function __construct(Manager $userManager,
View $view,
Connection $connection,
IConfig $config) {
$this->userManager = $userManager;
$this->view = $view;
$this->connection = $connection;
$this->config = $config;
parent::__construct();
}
protected function configure() {
$this
->setName('encryption:migrate')
->setDescription('initial migration to encryption 2.0')
->addArgument(
'user_id',
InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
'will migrate keys of the given user(s)'
);
}
protected function execute(InputInterface $input, OutputInterface $output) {
// perform system reorganization
$migration = new Migration($this->config, $this->view, $this->connection);
$users = $input->getArgument('user_id');
if (!empty($users)) {
foreach ($users as $user) {
if ($this->userManager->userExists($user)) {
$output->writeln("Migrating keys <info>$user</info>");
$migration->reorganizeFolderStructureForUser($user);
} else {
$output->writeln("<error>Unknown user $user</error>");
}
}
} else {
$output->writeln("Reorganize system folder structure");
$migration->reorganizeSystemFolderStructure();
$migration->updateDB();
foreach($this->userManager->getBackends() as $backend) {
$name = get_class($backend);
if ($backend instanceof IUserBackend) {
$name = $backend->getBackendName();
}
$output->writeln("Migrating keys for users on backend <info>$name</info>");
$limit = 500;
$offset = 0;
do {
$users = $backend->getUsers('', $limit, $offset);
foreach ($users as $user) {
$output->writeln(" <info>$user</info>");
$migration->reorganizeFolderStructureForUser($user);
}
$offset += $limit;
} while(count($users) >= $limit);
}
}
}
}

319
apps/encryption/lib/migration.php

@ -0,0 +1,319 @@
<?php
/**
* ownCloud
*
* @copyright (C) 2015 ownCloud, Inc.
*
* @author Bjoern Schiessle <schiessle@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Encryption;
use OC\DB\Connection;
use OC\Files\View;
use OCP\IConfig;
class Migration {
private $moduleId;
/** @var \OC\Files\View */
private $view;
/** @var \OC\DB\Connection */
private $connection;
/** @var IConfig */
private $config;
/**
* @param IConfig $config
* @param View $view
* @param Connection $connection
*/
public function __construct(IConfig $config, View $view, Connection $connection) {
$this->view = $view;
$this->view->getUpdater()->disable();
$this->connection = $connection;
$this->moduleId = \OCA\Encryption\Crypto\Encryption::ID;
$this->config = $config;
}
public function __destruct() {
$this->view->deleteAll('files_encryption/public_keys');
$this->updateFileCache();
}
/**
* update file cache, copy unencrypted_size to the 'size' column
*/
private function updateFileCache() {
$query = $this->connection->createQueryBuilder();
$query->update('`*PREFIX*filecache`')
->set('`size`', '`unencrypted_size`')
->where($query->expr()->eq('`encrypted`', ':encrypted'))
->setParameter('encrypted', 1);
$query->execute();
}
/**
* iterate through users and reorganize the folder structure
*/
public function reorganizeFolderStructure() {
$this->reorganizeSystemFolderStructure();
$limit = 500;
$offset = 0;
do {
$users = \OCP\User::getUsers('', $limit, $offset);
foreach ($users as $user) {
$this->reorganizeFolderStructureForUser($user);
}
$offset += $limit;
} while (count($users) >= $limit);
}
/**
* reorganize system wide folder structure
*/
public function reorganizeSystemFolderStructure() {
$this->createPathForKeys('/files_encryption');
// backup system wide folders
$this->backupSystemWideKeys();
// rename system wide mount point
$this->renameFileKeys('', '/files_encryption/keys');
// rename system private keys
$this->renameSystemPrivateKeys();
$storage = $this->view->getMount('')->getStorage();
$storage->getScanner()->scan('files_encryption');
}
/**
* reorganize folder structure for user
*
* @param string $user
*/
public function reorganizeFolderStructureForUser($user) {
// backup all keys
\OC_Util::tearDownFS();
\OC_Util::setupFS($user);
if ($this->backupUserKeys($user)) {
// rename users private key
$this->renameUsersPrivateKey($user);
$this->renameUsersPublicKey($user);
// rename file keys
$path = '/files_encryption/keys';
$this->renameFileKeys($user, $path);
$trashPath = '/files_trashbin/keys';
if (\OC_App::isEnabled('files_trashbin') && $this->view->is_dir($user . '/' . $trashPath)) {
$this->renameFileKeys($user, $trashPath, true);
$this->view->deleteAll($trashPath);
}
// delete old folders
$this->deleteOldKeys($user);
$this->view->getMount('/' . $user)->getStorage()->getScanner()->scan('files_encryption');
}
}
/**
* update database
*/
public function updateDB() {
// delete left-over from old encryption which is no longer needed
$this->config->deleteAppValue('files_encryption', 'installed_version');
$this->config->deleteAppValue('files_encryption', 'ocsid');
$this->config->deleteAppValue('files_encryption', 'types');
$this->config->deleteAppValue('files_encryption', 'enabled');
$query = $this->connection->createQueryBuilder();
$query->update('`*PREFIX*appconfig`')
->set('`appid`', ':newappid')
->where($query->expr()->eq('`appid`', ':oldappid'))
->setParameter('oldappid', 'files_encryption')
->setParameter('newappid', 'encryption');
$query->execute();
$query = $this->connection->createQueryBuilder();
$query->update('`*PREFIX*preferences`')
->set('`appid`', ':newappid')
->where($query->expr()->eq('`appid`', ':oldappid'))
->setParameter('oldappid', 'files_encryption')
->setParameter('newappid', 'encryption');
$query->execute();
}
/**
* create backup of system-wide keys
*/
private function backupSystemWideKeys() {
$backupDir = 'encryption_migration_backup_' . date("Y-m-d_H-i-s");
$this->view->mkdir($backupDir);
$this->view->copy('files_encryption', $backupDir . '/files_encryption');
}
/**
* create backup of user specific keys
*
* @param string $user
* @return bool
*/
private function backupUserKeys($user) {
$encryptionDir = $user . '/files_encryption';
if ($this->view->is_dir($encryptionDir)) {
$backupDir = $user . '/encryption_migration_backup_' . date("Y-m-d_H-i-s");
$this->view->mkdir($backupDir);
$this->view->copy($encryptionDir, $backupDir);
return true;
}
return false;
}
/**
* rename system-wide private keys
*/
private function renameSystemPrivateKeys() {
$dh = $this->view->opendir('files_encryption');
$this->createPathForKeys('/files_encryption/' . $this->moduleId );
if (is_resource($dh)) {
while (($privateKey = readdir($dh)) !== false) {
if (!\OC\Files\Filesystem::isIgnoredDir($privateKey) ) {
if (!$this->view->is_dir('/files_encryption/' . $privateKey)) {
$this->view->rename('files_encryption/' . $privateKey, 'files_encryption/' . $this->moduleId . '/' . $privateKey);
$this->renameSystemPublicKey($privateKey);
}
}
}
closedir($dh);
}
}
/**
* rename system wide public key
*
* @param $privateKey private key for which we want to rename the corresponding public key
*/
private function renameSystemPublicKey($privateKey) {
$publicKey = substr($privateKey,0 , strrpos($privateKey, '.privateKey')) . '.publicKey';
$this->view->rename('files_encryption/public_keys/' . $publicKey, 'files_encryption/' . $this->moduleId . '/' . $publicKey);
}
/**
* rename user-specific private keys
*
* @param string $user
*/
private function renameUsersPrivateKey($user) {
$oldPrivateKey = $user . '/files_encryption/' . $user . '.privateKey';
$newPrivateKey = $user . '/files_encryption/' . $this->moduleId . '/' . $user . '.privateKey';
$this->createPathForKeys(dirname($newPrivateKey));
$this->view->rename($oldPrivateKey, $newPrivateKey);
}
/**
* rename user-specific public keys
*
* @param string $user
*/
private function renameUsersPublicKey($user) {
$oldPublicKey = '/files_encryption/public_keys/' . $user . '.publicKey';
$newPublicKey = $user . '/files_encryption/' . $this->moduleId . '/' . $user . '.publicKey';
$this->createPathForKeys(dirname($newPublicKey));
$this->view->rename($oldPublicKey, $newPublicKey);
}
/**
* rename file keys
*
* @param string $user
* @param string $path
* @param bool $trash
*/
private function renameFileKeys($user, $path, $trash = false) {
$dh = $this->view->opendir($user . '/' . $path);
if (is_resource($dh)) {
while (($file = readdir($dh)) !== false) {
if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
if ($this->view->is_dir($user . '/' . $path . '/' . $file)) {
$this->renameFileKeys($user, $path . '/' . $file, $trash);
} else {
$target = $this->getTargetDir($user, $path, $file, $trash);
$this->createPathForKeys(dirname($target));
$this->view->rename($user . '/' . $path . '/' . $file, $target);
}
}
}
closedir($dh);
}
}
/**
* generate target directory
*
* @param string $user
* @param string $filePath
* @param string $filename
* @param bool $trash
* @return string
*/
private function getTargetDir($user, $filePath, $filename, $trash) {
if ($trash) {
$targetDir = $user . '/files_encryption/keys/files_trashbin/' . substr($filePath, strlen('/files_trashbin/keys/')) . '/' . $this->moduleId . '/' . $filename;
} else {
$targetDir = $user . '/files_encryption/keys/files/' . substr($filePath, strlen('/files_encryption/keys/')) . '/' . $this->moduleId . '/' . $filename;
}
return $targetDir;
}
/**
* delete old keys
*
* @param string $user
*/
private function deleteOldKeys($user) {
$this->view->deleteAll($user . '/files_encryption/keyfiles');
$this->view->deleteAll($user . '/files_encryption/share-keys');
}
/**
* create directories for the keys recursively
*
* @param string $path
*/
private function createPathForKeys($path) {
if (!$this->view->file_exists($path)) {
$sub_dirs = explode('/', $path);
$dir = '';
foreach ($sub_dirs as $sub_dir) {
$dir .= '/' . $sub_dir;
if (!$this->view->is_dir($dir)) {
$this->view->mkdir($dir);
}
}
}
}
}

356
apps/encryption/tests/lib/MigrationTest.php

@ -0,0 +1,356 @@
<?php
/**
* ownCloud
*
* @copyright (C) 2015 ownCloud, Inc.
*
* @author Bjoern Schiessle <schiessle@owncloud.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Encryption\Tests;
use OCA\Encryption\Migration;
class MigrationTest extends \Test\TestCase {
const TEST_ENCRYPTION_MIGRATION_USER1='test_encryption_user1';
const TEST_ENCRYPTION_MIGRATION_USER2='test_encryption_user2';
const TEST_ENCRYPTION_MIGRATION_USER3='test_encryption_user3';
/** @var \OC\Files\View */
private $view;
private $public_share_key_id = 'share_key_id';
private $recovery_key_id = 'recovery_key_id';
private $moduleId;
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
\OC_User::createUser(self::TEST_ENCRYPTION_MIGRATION_USER1, 'foo');
\OC_User::createUser(self::TEST_ENCRYPTION_MIGRATION_USER2, 'foo');
\OC_User::createUser(self::TEST_ENCRYPTION_MIGRATION_USER3, 'foo');
}
public static function tearDownAfterClass() {
\OC_User::deleteUser(self::TEST_ENCRYPTION_MIGRATION_USER1);
\OC_User::deleteUser(self::TEST_ENCRYPTION_MIGRATION_USER2);
\OC_User::deleteUser(self::TEST_ENCRYPTION_MIGRATION_USER3);
parent::tearDownAfterClass();
}
public function setUp() {
$this->view = new \OC\Files\View();
$this->moduleId = \OCA\Encryption\Crypto\Encryption::ID;
}
protected function createDummyShareKeys($uid) {
$this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/folder3/file3');
$this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/file2');
$this->view->mkdir($uid . '/files_encryption/keys/folder1/file.1');
$this->view->mkdir($uid . '/files_encryption/keys/folder2/file.2.1');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/folder3/file3/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/folder3/file3/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/folder3/file3/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/file2/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/file2/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/file2/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/file.1/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/file.1/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/file.1/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey' , 'data');
if ($this->public_share_key_id) {
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/' . $this->public_share_key_id . '.shareKey' , 'data');
}
if ($this->recovery_key_id) {
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/' . $this->recovery_key_id . '.shareKey' , 'data');
}
}
protected function createDummyUserKeys($uid) {
$this->view->mkdir($uid . '/files_encryption/');
$this->view->mkdir('/files_encryption/public_keys');
$this->view->file_put_contents($uid . '/files_encryption/' . $uid . '.privateKey', 'privateKey');
$this->view->file_put_contents('/files_encryption/public_keys/' . $uid . '.publicKey', 'publicKey');
}
protected function createDummyFileKeys($uid) {
$this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/folder3/file3');
$this->view->mkdir($uid . '/files_encryption/keys/folder1/folder2/file2');
$this->view->mkdir($uid . '/files_encryption/keys/folder1/file.1');
$this->view->mkdir($uid . '/files_encryption/keys/folder2/file.2.1');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/folder3/file3/fileKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/folder2/file2/fileKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder1/file.1/fileKey' , 'data');
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/fileKey' , 'data');
}
protected function createDummyFilesInTrash($uid) {
$this->view->mkdir($uid . '/files_trashbin/keys/file1.d5457864');
$this->view->mkdir($uid . '/files_trashbin/keys/folder1.d7437648723/file2');
$this->view->file_put_contents($uid . '/files_trashbin/keys/file1.d5457864/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_trashbin/keys/file1.d5457864/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_trashbin/keys/folder1.d7437648723/file2/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey' , 'data');
$this->view->file_put_contents($uid . '/files_trashbin/keys/file1.d5457864/fileKey' , 'data');
$this->view->file_put_contents($uid . '/files_trashbin/keys/folder1.d7437648723/file2/fileKey' , 'data');
}
protected function createDummySystemWideKeys() {
$this->view->mkdir('files_encryption');
$this->view->mkdir('files_encryption/public_keys');
$this->view->file_put_contents('files_encryption/systemwide_1.privateKey', 'data');
$this->view->file_put_contents('files_encryption/systemwide_2.privateKey', 'data');
$this->view->file_put_contents('files_encryption/public_keys/systemwide_1.publicKey', 'data');
$this->view->file_put_contents('files_encryption/public_keys/systemwide_2.publicKey', 'data');
}
public function testMigrateToNewFolderStructure() {
$this->createDummyUserKeys(self::TEST_ENCRYPTION_MIGRATION_USER1);
$this->createDummyUserKeys(self::TEST_ENCRYPTION_MIGRATION_USER2);
$this->createDummyUserKeys(self::TEST_ENCRYPTION_MIGRATION_USER3);
$this->createDummyShareKeys(self::TEST_ENCRYPTION_MIGRATION_USER1);
$this->createDummyShareKeys(self::TEST_ENCRYPTION_MIGRATION_USER2);
$this->createDummyShareKeys(self::TEST_ENCRYPTION_MIGRATION_USER3);
$this->createDummyFileKeys(self::TEST_ENCRYPTION_MIGRATION_USER1);
$this->createDummyFileKeys(self::TEST_ENCRYPTION_MIGRATION_USER2);
$this->createDummyFileKeys(self::TEST_ENCRYPTION_MIGRATION_USER3);
$this->createDummyFilesInTrash(self::TEST_ENCRYPTION_MIGRATION_USER2);
// no user for system wide mount points
$this->createDummyFileKeys('');
$this->createDummyShareKeys('');
$this->createDummySystemWideKeys();
$m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection());
$m->reorganizeFolderStructure();
$this->assertTrue(
$this->view->file_exists(
self::TEST_ENCRYPTION_MIGRATION_USER1 . '/files_encryption/' .
$this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.publicKey')
);
$this->assertTrue(
$this->view->file_exists(
self::TEST_ENCRYPTION_MIGRATION_USER2 . '/files_encryption/' .
$this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.publicKey')
);
$this->assertTrue(
$this->view->file_exists(
self::TEST_ENCRYPTION_MIGRATION_USER3 . '/files_encryption/' .
$this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.publicKey')
);
$this->assertTrue(
$this->view->file_exists(
'/files_encryption/' . $this->moduleId . '/systemwide_1.publicKey')
);
$this->assertTrue(
$this->view->file_exists(
'/files_encryption/' . $this->moduleId . '/systemwide_2.publicKey')
);
$this->verifyNewKeyPath(self::TEST_ENCRYPTION_MIGRATION_USER1);
$this->verifyNewKeyPath(self::TEST_ENCRYPTION_MIGRATION_USER2);
$this->verifyNewKeyPath(self::TEST_ENCRYPTION_MIGRATION_USER3);
// system wide keys
$this->verifyNewKeyPath('');
// trash
$this->verifyFilesInTrash(self::TEST_ENCRYPTION_MIGRATION_USER2);
}
protected function verifyFilesInTrash($uid) {
// share keys
$this->assertTrue(
$this->view->file_exists($uid . '/files_encryption/keys/files_trashbin/file1.d5457864/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey')
);
$this->assertTrue(
$this->view->file_exists($uid . '/files_encryption/keys/files_trashbin/file1.d5457864/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey')
);
$this->assertTrue(
$this->view->file_exists($uid . '/files_encryption/keys/files_trashbin/folder1.d7437648723/file2/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey')
);
// file keys
$this->assertTrue(
$this->view->file_exists($uid . '/files_encryption/keys/files_trashbin/file1.d5457864/' . $this->moduleId . '/fileKey')
);
$this->assertTrue(
$this->view->file_exists($uid . '/files_encryption/keys/files_trashbin/file1.d5457864/' . $this->moduleId . '/fileKey')
);
$this->assertTrue(
$this->view->file_exists($uid . '/files_encryption/keys/files_trashbin/folder1.d7437648723/file2/' . $this->moduleId . '/fileKey')
);
}
protected function verifyNewKeyPath($uid) {
// private key
if ($uid !== '') {
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/' . $this->moduleId . '/'. $uid . '.privateKey'));
}
// file keys
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/folder3/file3/' . $this->moduleId . '/fileKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/file2/' . $this->moduleId . '/fileKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/file.1/' . $this->moduleId . '/fileKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder2/file.2.1/' .$this->moduleId . '/fileKey'));
// share keys
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/folder3/file3/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/folder3/file3/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/folder3/file3/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/file2/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/file2/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/folder2/file2/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/file.1/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/file.1/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder1/file.1/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder2/file.2.1/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER1 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder2/file.2.1/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER2 . '.shareKey'));
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder2/file.2.1/' . $this->moduleId . '/' . self::TEST_ENCRYPTION_MIGRATION_USER3 . '.shareKey'));
if ($this->public_share_key_id) {
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder2/file.2.1/' . $this->moduleId . '/' . $this->public_share_key_id . '.shareKey'));
}
if ($this->recovery_key_id) {
$this->assertTrue($this->view->file_exists($uid . '/files_encryption/keys/files/folder2/file.2.1/' . $this->moduleId . '/' . $this->recovery_key_id . '.shareKey'));
}
}
private function prepareDB() {
$config = \OC::$server->getConfig();
$config->setAppValue('files_encryption', 'recoveryKeyId', 'recovery_id');
$config->setAppValue('files_encryption', 'publicShareKeyId', 'share_id');
$config->setAppValue('files_encryption', 'recoveryAdminEnabled', '1');
$config->setUserValue(self::TEST_ENCRYPTION_MIGRATION_USER1, 'files_encryption', 'recoverKeyEnabled', '1');
// delete default values set by the encryption app during initialization
/** @var \OC\DB\Connection $connection */
$connection = \OC::$server->getDatabaseConnection();
$query = $connection->createQueryBuilder();
$query->delete('`*PREFIX*appconfig`')
->where($query->expr()->eq('`appid`', ':appid'))
->setParameter('appid', 'encryption');
$query->execute();
$query = $connection->createQueryBuilder();
$query->delete('`*PREFIX*preferences`')
->where($query->expr()->eq('`appid`', ':appid'))
->setParameter('appid', 'encryption');
$query->execute();
}
public function testUpdateDB() {
$this->prepareDB();
$m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection());
$m->updateDB();
$this->verifyDB('`*PREFIX*appconfig`', 'files_encryption', 0);
$this->verifyDB('`*PREFIX*preferences`', 'files_encryption', 0);
$this->verifyDB('`*PREFIX*appconfig`', 'encryption', 3);
$this->verifyDB('`*PREFIX*preferences`', 'encryption', 1);
}
public function verifyDB($table, $appid, $expected) {
/** @var \OC\DB\Connection $connection */
$connection = \OC::$server->getDatabaseConnection();
$query = $connection->createQueryBuilder();
$query->select('`appid`')
->from($table)
->where($query->expr()->eq('`appid`', ':appid'))
->setParameter('appid', $appid);
$result = $query->execute();
$values = $result->fetchAll();
$this->assertSame($expected,
count($values)
);
}
/**
* test update of the file cache
*/
public function testUpdateFileCache() {
$this->prepareFileCache();
$m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection());
\Test_Helper::invokePrivate($m, 'updateFileCache');
// check results
/** @var \OC\DB\Connection $connection */
$connection = \OC::$server->getDatabaseConnection();
$query = $connection->createQueryBuilder();
$query->select('*')
->from('`*PREFIX*filecache`');
$result = $query->execute();
$entries = $result->fetchAll();
foreach($entries as $entry) {
if ((int)$entry['encrypted'] === 1) {
$this->assertSame((int)$entry['unencrypted_size'], (int)$entry['size']);
} else {
$this->assertSame((int)$entry['unencrypted_size'] - 2, (int)$entry['size']);
}
}
}
public function prepareFileCache() {
/** @var \OC\DB\Connection $connection */
$connection = \OC::$server->getDatabaseConnection();
$query = $connection->createQueryBuilder();
$query->delete('`*PREFIX*filecache`');
$query->execute();
$query = $connection->createQueryBuilder();
$result = $query->select('`fileid`')
->from('`*PREFIX*filecache`')
->setMaxResults(1)->execute()->fetchAll();
$this->assertEmpty($result);
$query = $connection->createQueryBuilder();
$query->insert('`*PREFIX*filecache`')
->values(
array(
'`storage`' => ':storage',
'`path_hash`' => ':path_hash',
'`encrypted`' => ':encrypted',
'`size`' => ':size',
'`unencrypted_size`' => ':unencrypted_size'
)
);
for ($i = 1; $i < 20; $i++) {
$query->setParameter('storage', 1)
->setParameter('path_hash', $i)
->setParameter('encrypted', $i % 2)
->setParameter('size', $i)
->setParameter('unencrypted_size', $i + 2);
$this->assertSame(1,
$query->execute()
);
}
$query = $connection->createQueryBuilder();
$result = $query->select('`fileid`')
->from('`*PREFIX*filecache`')
->execute()->fetchAll();
$this->assertSame(19, count($result));
}
}

42
lib/private/encryption/manager.php

@ -22,24 +22,34 @@
namespace OC\Encryption;
use OC\Files\Storage\Shared;
use OC\Files\Storage\Wrapper\Encryption;
use OC\Files\View;
use OCP\Encryption\IEncryptionModule;
use OCP\Encryption\IManager;
use OCP\Files\Mount\IMountPoint;
use OCP\IConfig;
use OCP\ILogger;
class Manager implements \OCP\Encryption\IManager {
class Manager implements IManager {
/** @var array */
protected $encryptionModules;
/** @var \OCP\IConfig */
/** @var IConfig */
protected $config;
/** @var ILogger */
protected $logger;
/**
* @param \OCP\IConfig $config
* @param IConfig $config
* @param ILogger $logger
*/
public function __construct(\OCP\IConfig $config) {
public function __construct(IConfig $config, ILogger $logger) {
$this->encryptionModules = array();
$this->config = $config;
$this->logger = $logger;
}
/**
@ -58,6 +68,24 @@ class Manager implements \OCP\Encryption\IManager {
return $enabled === 'yes';
}
/**
* check if new encryption is ready
*
* @return boolean
*/
public function isReady() {
// check if we are still in transit between the old and the new encryption
$oldEncryption = $this->config->getAppValue('files_encryption', 'installed_version');
if (!empty($oldEncryption)) {
$warning = 'Installation is in transit between the old Encryption (ownCloud <= 8.0)
and the new encryption. Please enable the "ownCloud Default Encryption Module"
and run \'occ encryption:migrate\'';
$this->logger->warning($warning);
return false;
}
return true;
}
/**
* Registers an encryption module
*
@ -185,10 +213,10 @@ class Manager implements \OCP\Encryption\IManager {
'mountPoint' => $mountPoint,
'mount' => $mount];
if (!($storage instanceof \OC\Files\Storage\Shared)) {
if (!($storage instanceof Shared)) {
$manager = \OC::$server->getEncryptionManager();
$util = new \OC\Encryption\Util(
new \OC\Files\View(), \OC::$server->getUserManager(), \OC::$server->getConfig());
$util = new Util(
new View(), \OC::$server->getUserManager(), \OC::$server->getConfig());
$user = \OC::$server->getUserSession()->getUser();
$logger = \OC::$server->getLogger();
$uid = $user ? $user->getUID() : null;

10
lib/private/encryption/util.php

@ -24,6 +24,7 @@ namespace OC\Encryption;
use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException;
use OC\Encryption\Exceptions\EncryptionHeaderToLargeException;
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
use OC\Files\View;
use OCP\Encryption\IEncryptionModule;
use OCP\IConfig;
@ -92,6 +93,7 @@ class Util {
*
* @param array $header
* @return string
* @throws ModuleDoesNotExistsException
*/
public function getEncryptionModuleId(array $header = null) {
$id = '';
@ -99,6 +101,14 @@ class Util {
if (isset($header[$encryptionModuleKey])) {
$id = $header[$encryptionModuleKey];
} elseif (isset($header['cipher'])) {
if (class_exists('\OCA\Encryption\Crypto\Encryption')) {
// fall back to default encryption if the user migrated from
// ownCloud <= 8.0 with the old encryption
$id = \OCA\Encryption\Crypto\Encryption::ID;
} else {
throw new ModuleDoesNotExistsException('ownCloud default encryption module missing');
}
}
return $id;

11
lib/private/files/storage/wrapper/encryption.php

@ -154,7 +154,8 @@ class Encryption extends Wrapper {
* @return bool
*/
public function unlink($path) {
if ($this->util->isExcluded($path)) {
$fullPath = $this->getFullPath($path);
if ($this->util->isExcluded($fullPath)) {
return $this->storage->unlink($path);
}
@ -175,7 +176,8 @@ class Encryption extends Wrapper {
* @return bool
*/
public function rename($path1, $path2) {
if ($this->util->isExcluded($path1)) {
$fullPath1 = $this->getFullPath($path1);
if ($this->util->isExcluded($fullPath1)) {
return $this->storage->rename($path1, $path2);
}
@ -204,8 +206,9 @@ class Encryption extends Wrapper {
* @return bool
*/
public function copy($path1, $path2) {
if ($this->util->isExcluded($path1)) {
return $this->storage->rename($path1, $path2);
$fullPath1 = $this->getFullPath($path1);
if ($this->util->isExcluded($fullPath1)) {
return $this->storage->copy($path1, $path2);
}
$source = $this->getFullPath($path1);

2
lib/private/server.php

@ -84,7 +84,7 @@ class Server extends SimpleContainer implements IServerContainer {
});
$this->registerService('EncryptionManager', function (Server $c) {
return new Encryption\Manager($c->getConfig());
return new Encryption\Manager($c->getConfig(), $c->getLogger());
});
$this->registerService('EncryptionFileHelper', function (Server $c) {

1
settings/admin.php

@ -82,6 +82,7 @@ $excludedGroupsList = $appConfig->getValue('core', 'shareapi_exclude_groups_list
$excludedGroupsList = explode(',', $excludedGroupsList); // FIXME: this should be JSON!
$template->assign('shareExcludedGroupsList', implode('|', $excludedGroupsList));
$template->assign('encryptionEnabled', \OC::$server->getEncryptionManager()->isEnabled());
$template->assign('encryptionReady', \OC::$server->getEncryptionManager()->isReady());
$encryptionModules = \OC::$server->getEncryptionManager()->getEncryptionModules();
try {
$defaultEncryptionModule = \OC::$server->getEncryptionManager()->getDefaultEncryptionModule();

9
settings/templates/admin.php

@ -307,8 +307,11 @@ if ($_['cronErrors']) {
<label for="encryptionEnabled"><?php p($l->t('Enable Server-Side-Encryption'));?></label><br/>
</p>
<div id='selectEncryptionModules' class="<?php if (!$_['encryptionEnabled']) { p('hidden'); }?>">
<?php if (empty($_['encryptionModules'])): p('No encryption module loaded, please load a encryption module in the app menu');
else: ?>
<?php if ($_['encryptionReady'] === false) {
p('Seems like you are in transit from the old encryption (ownCloud <= 8.0) to the new one. Please enable the "ownCloud Default Encryption Module" and run \'occ encryption:migrate\'');
} elseif (empty($_['encryptionModules'])) {
p('No encryption module loaded, please load a encryption module in the app menu');
} else { ?>
<h3>Select default encryption module:</h3>
<fieldset id='encryptionModules'>
<?php foreach ($_['encryptionModules'] as $id => $module): ?>
@ -319,7 +322,7 @@ if ($_['cronErrors']) {
<label for="<?php p($id) ?>"><?php p($module['displayName']) ?></label><br />
<?php endforeach;?>
</fieldset>
<?php endif; ?>
<?php } ?>
</div>
</div>

95
tests/lib/encryption/managertest.php

@ -7,36 +7,45 @@ use Test\TestCase;
class ManagerTest extends TestCase {
/** @var Manager */
private $manager;
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $config;
/** @var \PHPUnit_Framework_MockObject_MockObject */
private $logger;
public function setUp() {
parent::setUp();
$this->config = $this->getMock('\OCP\IConfig');
$this->logger = $this->getMock('\OCP\ILogger');
$this->manager = new Manager($this->config, $this->logger);
}
public function testManagerIsDisabled() {
$config = $this->getMock('\OCP\IConfig');
$m = new Manager($config);
$this->assertFalse($m->isEnabled());
$this->assertFalse($this->manager->isEnabled());
}
public function testManagerIsDisabledIfEnabledButNoModules() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getAppValue')->willReturn(true);
$m = new Manager($config);
$this->assertFalse($m->isEnabled());
$this->config->expects($this->any())->method('getAppValue')->willReturn(true);
$this->assertFalse($this->manager->isEnabled());
}
public function testManagerIsDisabledIfDisabledButModules() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getAppValue')->willReturn(false);
$this->config->expects($this->any())->method('getAppValue')->willReturn(false);
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
$em->expects($this->any())->method('getId')->willReturn(0);
$em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
$m = new Manager($config);
$m->registerEncryptionModule($em);
$this->assertFalse($m->isEnabled());
$this->manager->registerEncryptionModule($em);
$this->assertFalse($this->manager->isEnabled());
}
public function testManagerIsEnabled() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getSystemValue')->willReturn(true);
$config->expects($this->any())->method('getAppValue')->willReturn('yes');
$m = new Manager($config);
$this->assertTrue($m->isEnabled());
$this->config->expects($this->any())->method('getSystemValue')->willReturn(true);
$this->config->expects($this->any())->method('getAppValue')->willReturn('yes');
$this->assertTrue($this->manager->isEnabled());
}
/**
@ -44,30 +53,26 @@ class ManagerTest extends TestCase {
* @expectedExceptionMessage Id "0" already used by encryption module "TestDummyModule0"
*/
public function testModuleRegistration() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getAppValue')->willReturn('yes');
$this->config->expects($this->any())->method('getAppValue')->willReturn('yes');
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
$em->expects($this->any())->method('getId')->willReturn(0);
$em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
$m = new Manager($config);
$m->registerEncryptionModule($em);
$this->assertSame(1, count($m->getEncryptionModules()));
$m->registerEncryptionModule($em);
$this->manager->registerEncryptionModule($em);
$this->assertSame(1, count($this->manager->getEncryptionModules()));
$this->manager->registerEncryptionModule($em);
}
public function testModuleUnRegistration() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getAppValue')->willReturn(true);
$this->config->expects($this->any())->method('getAppValue')->willReturn(true);
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
$em->expects($this->any())->method('getId')->willReturn(0);
$em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
$m = new Manager($config);
$m->registerEncryptionModule($em);
$this->manager->registerEncryptionModule($em);
$this->assertSame(1,
count($m->getEncryptionModules())
count($this->manager->getEncryptionModules())
);
$m->unregisterEncryptionModule($em);
$this->assertEmpty($m->getEncryptionModules());
$this->manager->unregisterEncryptionModule($em);
$this->assertEmpty($this->manager->getEncryptionModules());
}
/**
@ -75,40 +80,34 @@ class ManagerTest extends TestCase {
* @expectedExceptionMessage Module with id: unknown does not exists.
*/
public function testGetEncryptionModuleUnknown() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getAppValue')->willReturn(true);
$this->config->expects($this->any())->method('getAppValue')->willReturn(true);
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
$em->expects($this->any())->method('getId')->willReturn(0);
$em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
$m = new Manager($config);
$m->registerEncryptionModule($em);
$this->assertSame(1, count($m->getEncryptionModules()));
$m->getEncryptionModule('unknown');
$this->manager->registerEncryptionModule($em);
$this->assertSame(1, count($this->manager->getEncryptionModules()));
$this->manager->getEncryptionModule('unknown');
}
public function testGetEncryptionModule() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getAppValue')->willReturn(true);
$this->config->expects($this->any())->method('getAppValue')->willReturn(true);
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
$em->expects($this->any())->method('getId')->willReturn(0);
$em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
$m = new Manager($config);
$m->registerEncryptionModule($em);
$this->assertSame(1, count($m->getEncryptionModules()));
$en0 = $m->getEncryptionModule(0);
$this->manager->registerEncryptionModule($em);
$this->assertSame(1, count($this->manager->getEncryptionModules()));
$en0 = $this->manager->getEncryptionModule(0);
$this->assertEquals(0, $en0->getId());
}
public function testGetDefaultEncryptionModule() {
$config = $this->getMock('\OCP\IConfig');
$config->expects($this->any())->method('getAppValue')->willReturn(true);
$this->config->expects($this->any())->method('getAppValue')->willReturn(true);
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
$em->expects($this->any())->method('getId')->willReturn(0);
$em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0');
$m = new Manager($config);
$m->registerEncryptionModule($em);
$this->assertSame(1, count($m->getEncryptionModules()));
$en0 = $m->getEncryptionModule(0);
$this->manager->registerEncryptionModule($em);
$this->assertSame(1, count($this->manager->getEncryptionModules()));
$en0 = $this->manager->getEncryptionModule(0);
$this->assertEquals(0, $en0->getId());
}

Loading…
Cancel
Save