aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/encryption/appinfo/register_command.php3
-rw-r--r--apps/encryption/command/migratekeys.php10
-rw-r--r--apps/encryption/lib/migration.php74
-rw-r--r--apps/encryption/tests/lib/MigrationTest.php49
-rw-r--r--lib/private/app.php4
-rw-r--r--lib/private/encryption/util.php29
-rw-r--r--lib/private/files/storage/dav.php44
-rw-r--r--lib/private/files/storage/wrapper/encryption.php110
-rw-r--r--settings/application.php3
-rw-r--r--settings/controller/encryptioncontroller.php10
-rw-r--r--tests/lib/encryption/utiltest.php13
-rw-r--r--tests/lib/files/storage/wrapper/encryption.php120
12 files changed, 362 insertions, 107 deletions
diff --git a/apps/encryption/appinfo/register_command.php b/apps/encryption/appinfo/register_command.php
index f727fdf9d70..4fdf7ecec38 100644
--- a/apps/encryption/appinfo/register_command.php
+++ b/apps/encryption/appinfo/register_command.php
@@ -26,4 +26,5 @@ $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));
+$logger = \OC::$server->getLogger();
+$application->add(new MigrateKeys($userManager, $view, $connection, $config, $logger));
diff --git a/apps/encryption/command/migratekeys.php b/apps/encryption/command/migratekeys.php
index d0fc1573061..7e320102172 100644
--- a/apps/encryption/command/migratekeys.php
+++ b/apps/encryption/command/migratekeys.php
@@ -27,6 +27,7 @@ use OC\Files\View;
use OC\User\Manager;
use OCA\Encryption\Migration;
use OCP\IConfig;
+use OCP\ILogger;
use OCP\IUserBackend;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -44,22 +45,27 @@ class MigrateKeys extends Command {
private $connection;
/** @var IConfig */
private $config;
+ /** @var ILogger */
+ private $logger;
/**
* @param Manager $userManager
* @param View $view
* @param Connection $connection
* @param IConfig $config
+ * @param ILogger $logger
*/
public function __construct(Manager $userManager,
View $view,
Connection $connection,
- IConfig $config) {
+ IConfig $config,
+ ILogger $logger) {
$this->userManager = $userManager;
$this->view = $view;
$this->connection = $connection;
$this->config = $config;
+ $this->logger = $logger;
parent::__construct();
}
@@ -77,7 +83,7 @@ class MigrateKeys extends Command {
protected function execute(InputInterface $input, OutputInterface $output) {
// perform system reorganization
- $migration = new Migration($this->config, $this->view, $this->connection);
+ $migration = new Migration($this->config, $this->view, $this->connection, $this->logger);
$users = $input->getArgument('user_id');
if (!empty($users)) {
diff --git a/apps/encryption/lib/migration.php b/apps/encryption/lib/migration.php
index 26e2a143f69..0903587e879 100644
--- a/apps/encryption/lib/migration.php
+++ b/apps/encryption/lib/migration.php
@@ -26,6 +26,7 @@ namespace OCA\Encryption;
use OC\DB\Connection;
use OC\Files\View;
use OCP\IConfig;
+use OCP\ILogger;
class Migration {
@@ -37,17 +38,22 @@ class Migration {
/** @var IConfig */
private $config;
+ /** @var ILogger */
+ private $logger;
+
/**
* @param IConfig $config
* @param View $view
* @param Connection $connection
+ * @param ILogger $logger
*/
- public function __construct(IConfig $config, View $view, Connection $connection) {
+ public function __construct(IConfig $config, View $view, Connection $connection, ILogger $logger) {
$this->view = $view;
$this->view->getUpdater()->disable();
$this->connection = $connection;
$this->moduleId = \OCA\Encryption\Crypto\Encryption::ID;
$this->config = $config;
+ $this->logger = $logger;
}
public function finalCleanUp() {
@@ -234,9 +240,10 @@ class Migration {
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);
+ if ($this->view->file_exists($oldPrivateKey)) {
+ $this->createPathForKeys(dirname($newPrivateKey));
+ $this->view->rename($oldPrivateKey, $newPrivateKey);
+ }
}
/**
@@ -247,9 +254,10 @@ class Migration {
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);
+ if ($this->view->file_exists($oldPublicKey)) {
+ $this->createPathForKeys(dirname($newPublicKey));
+ $this->view->rename($oldPublicKey, $newPublicKey);
+ }
}
/**
@@ -261,6 +269,11 @@ class Migration {
*/
private function renameFileKeys($user, $path, $trash = false) {
+ if ($this->view->is_dir($user . '/' . $path) === false) {
+ $this->logger->info('Skip dir /' . $user . '/' . $path . ': does not exist');
+ return;
+ }
+
$dh = $this->view->opendir($user . '/' . $path);
if (is_resource($dh)) {
@@ -270,8 +283,15 @@ class Migration {
$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);
+ if ($target) {
+ $this->createPathForKeys(dirname($target));
+ $this->view->rename($user . '/' . $path . '/' . $file, $target);
+ } else {
+ $this->logger->warning(
+ 'did not move key "' . $file
+ . '" could not find the corresponding file in /data/' . $user . '/files.'
+ . 'Most likely the key was already moved in a previous migration run and is already on the right place.');
+ }
}
}
}
@@ -280,22 +300,48 @@ class Migration {
}
/**
+ * get system mount points
+ * wrap static method so that it can be mocked for testing
+ *
+ * @return array
+ */
+ protected function getSystemMountPoints() {
+ return \OC_Mount_Config::getSystemMountPoints();
+ }
+
+ /**
* generate target directory
*
* @param string $user
- * @param string $filePath
+ * @param string $keyPath
* @param string $filename
* @param bool $trash
* @return string
*/
- private function getTargetDir($user, $filePath, $filename, $trash) {
+ private function getTargetDir($user, $keyPath, $filename, $trash) {
if ($trash) {
- $targetDir = $user . '/files_encryption/keys/files_trashbin/' . substr($filePath, strlen('/files_trashbin/keys/')) . '/' . $this->moduleId . '/' . $filename;
+ $filePath = substr($keyPath, strlen('/files_trashbin/keys/'));
+ $targetDir = $user . '/files_encryption/keys/files_trashbin/' . $filePath . '/' . $this->moduleId . '/' . $filename;
} else {
- $targetDir = $user . '/files_encryption/keys/files/' . substr($filePath, strlen('/files_encryption/keys/')) . '/' . $this->moduleId . '/' . $filename;
+ $filePath = substr($keyPath, strlen('/files_encryption/keys/'));
+ $targetDir = $user . '/files_encryption/keys/files/' . $filePath . '/' . $this->moduleId . '/' . $filename;
}
- return $targetDir;
+ if ($user === '') {
+ // for system wide mounts we need to check if the mount point really exists
+ $normalized = trim($filePath, '/');
+ $systemMountPoints = $this->getSystemMountPoints();
+ foreach ($systemMountPoints as $mountPoint) {
+ if (strpos($normalized, $mountPoint['mountpoint']) === 0)
+ return $targetDir;
+ }
+ } else if ($trash === false && $this->view->file_exists('/' . $user. '/files/' . $filePath)) {
+ return $targetDir;
+ } else if ($trash === true && $this->view->file_exists('/' . $user. '/files_trashbin/' . $filePath)) {
+ return $targetDir;
+ }
+
+ return false;
}
/**
diff --git a/apps/encryption/tests/lib/MigrationTest.php b/apps/encryption/tests/lib/MigrationTest.php
index de1e2bd268b..a83909ac628 100644
--- a/apps/encryption/tests/lib/MigrationTest.php
+++ b/apps/encryption/tests/lib/MigrationTest.php
@@ -24,6 +24,7 @@
namespace OCA\Encryption\Tests;
use OCA\Encryption\Migration;
+use OCP\ILogger;
class MigrationTest extends \Test\TestCase {
@@ -37,6 +38,9 @@ class MigrationTest extends \Test\TestCase {
private $recovery_key_id = 'recovery_key_id';
private $moduleId;
+ /** @var PHPUnit_Framework_MockObject_MockObject | ILogger */
+ private $logger;
+
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
\OC_User::createUser(self::TEST_ENCRYPTION_MIGRATION_USER1, 'foo');
@@ -53,6 +57,7 @@ class MigrationTest extends \Test\TestCase {
public function setUp() {
+ $this->logger = $this->getMockBuilder('\OCP\ILogger')->disableOriginalConstructor()->getMock();
$this->view = new \OC\Files\View();
$this->moduleId = \OCA\Encryption\Crypto\Encryption::ID;
}
@@ -100,6 +105,17 @@ class MigrationTest extends \Test\TestCase {
$this->view->file_put_contents($uid . '/files_encryption/keys/folder2/file.2.1/fileKey' , 'data');
}
+ protected function createDummyFiles($uid) {
+ $this->view->mkdir($uid . '/files/folder1/folder2/folder3/file3');
+ $this->view->mkdir($uid . '/files/folder1/folder2/file2');
+ $this->view->mkdir($uid . '/files/folder1/file.1');
+ $this->view->mkdir($uid . '/files/folder2/file.2.1');
+ $this->view->file_put_contents($uid . '/files/folder1/folder2/folder3/file3/fileKey' , 'data');
+ $this->view->file_put_contents($uid . '/files/folder1/folder2/file2/fileKey' , 'data');
+ $this->view->file_put_contents($uid . '/files/folder1/file.1/fileKey' , 'data');
+ $this->view->file_put_contents($uid . '/files/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');
@@ -109,6 +125,11 @@ class MigrationTest extends \Test\TestCase {
$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');
+
+ // create the files itself
+ $this->view->mkdir($uid . '/files_trashbin/folder1.d7437648723');
+ $this->view->file_put_contents($uid . '/files_trashbin/file1.d5457864' , 'data');
+ $this->view->file_put_contents($uid . '/files_trashbin/folder1.d7437648723/file2' , 'data');
}
protected function createDummySystemWideKeys() {
@@ -118,7 +139,6 @@ class MigrationTest extends \Test\TestCase {
$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() {
@@ -134,6 +154,10 @@ class MigrationTest extends \Test\TestCase {
$this->createDummyFileKeys(self::TEST_ENCRYPTION_MIGRATION_USER2);
$this->createDummyFileKeys(self::TEST_ENCRYPTION_MIGRATION_USER3);
+ $this->createDummyFiles(self::TEST_ENCRYPTION_MIGRATION_USER1);
+ $this->createDummyFiles(self::TEST_ENCRYPTION_MIGRATION_USER2);
+ $this->createDummyFiles(self::TEST_ENCRYPTION_MIGRATION_USER3);
+
$this->createDummyFilesInTrash(self::TEST_ENCRYPTION_MIGRATION_USER2);
// no user for system wide mount points
@@ -142,7 +166,22 @@ class MigrationTest extends \Test\TestCase {
$this->createDummySystemWideKeys();
- $m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection());
+ $m = $this->getMockBuilder('OCA\Encryption\Migration')
+ ->setConstructorArgs(
+ [
+ \OC::$server->getConfig(),
+ new \OC\Files\View(),
+ \OC::$server->getDatabaseConnection(),
+ $this->logger
+ ]
+ )->setMethods(['getSystemMountPoints'])->getMock();
+
+ $m->expects($this->any())->method('getSystemMountPoints')
+ ->willReturn([['mountpoint' => 'folder1'], ['mountpoint' => 'folder2']]);
+
+ //$m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection(), $this->logger);
+ $m->reorganizeFolderStructure();
+ // even if it runs twice folder should always move only once
$m->reorganizeFolderStructure();
$this->assertTrue(
@@ -267,7 +306,7 @@ class MigrationTest extends \Test\TestCase {
public function testUpdateDB() {
$this->prepareDB();
- $m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection());
+ $m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection(), $this->logger);
$m->updateDB();
$this->verifyDB('`*PREFIX*appconfig`', 'files_encryption', 0);
@@ -286,7 +325,7 @@ class MigrationTest extends \Test\TestCase {
$config->setAppValue('encryption', 'publicShareKeyId', 'wrong_share_id');
$config->setUserValue(self::TEST_ENCRYPTION_MIGRATION_USER1, 'encryption', 'recoverKeyEnabled', '9');
- $m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection());
+ $m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection(), $this->logger);
$m->updateDB();
$this->verifyDB('`*PREFIX*appconfig`', 'files_encryption', 0);
@@ -349,7 +388,7 @@ class MigrationTest extends \Test\TestCase {
*/
public function testUpdateFileCache() {
$this->prepareFileCache();
- $m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection());
+ $m = new Migration(\OC::$server->getConfig(), new \OC\Files\View(), \OC::$server->getDatabaseConnection(), $this->logger);
self::invokePrivate($m, 'updateFileCache');
// check results
diff --git a/lib/private/app.php b/lib/private/app.php
index 5ce64f2ce32..ab3430bd51a 100644
--- a/lib/private/app.php
+++ b/lib/private/app.php
@@ -1168,9 +1168,7 @@ class OC_App {
OC_DB::updateDbFromStructure(self::getAppPath($appId) . '/appinfo/database.xml');
}
unset(self::$appVersion[$appId]);
- if (!self::isEnabled($appId)) {
- return false;
- }
+ // run upgrade code
if (file_exists(self::getAppPath($appId) . '/appinfo/update.php')) {
self::loadApp($appId, false);
include self::getAppPath($appId) . '/appinfo/update.php';
diff --git a/lib/private/encryption/util.php b/lib/private/encryption/util.php
index 8bff65428d3..d0733941a35 100644
--- a/lib/private/encryption/util.php
+++ b/lib/private/encryption/util.php
@@ -128,35 +128,6 @@ class Util {
}
/**
- * read header into array
- *
- * @param string $header
- * @return array
- */
- public function readHeader($header) {
-
- $result = array();
-
- if (substr($header, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
- $endAt = strpos($header, self::HEADER_END);
- if ($endAt !== false) {
- $header = substr($header, 0, $endAt + strlen(self::HEADER_END));
-
- // +1 to not start with an ':' which would result in empty element at the beginning
- $exploded = explode(':', substr($header, strlen(self::HEADER_START)+1));
-
- $element = array_shift($exploded);
- while ($element !== self::HEADER_END) {
- $result[$element] = array_shift($exploded);
- $element = array_shift($exploded);
- }
- }
- }
-
- return $result;
- }
-
- /**
* create header for encrypted file
*
* @param array $headerData
diff --git a/lib/private/files/storage/dav.php b/lib/private/files/storage/dav.php
index e02f971b38b..24cf3c29209 100644
--- a/lib/private/files/storage/dav.php
+++ b/lib/private/files/storage/dav.php
@@ -219,9 +219,9 @@ class DAV extends Common {
$this->statCache->set($path, false);
return false;
}
- $this->convertException($e);
+ $this->convertException($e, $path);
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
}
return false;
}
@@ -286,9 +286,9 @@ class DAV extends Common {
if ($e->getHttpStatus() === 404) {
return false;
}
- $this->convertException($e);
+ $this->convertException($e, $path);
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
}
return false;
}
@@ -311,9 +311,9 @@ class DAV extends Common {
if ($e->getHttpStatus() === 404) {
return false;
}
- $this->convertException($e);
+ $this->convertException($e, $path);
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
}
return false;
}
@@ -363,6 +363,9 @@ class DAV extends Common {
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($statusCode !== 200) {
Util::writeLog("webdav client", 'curl GET ' . curl_getinfo($curl, CURLINFO_EFFECTIVE_URL) . ' returned status code ' . $statusCode, Util::ERROR);
+ if ($statusCode === 423) {
+ throw new \OCP\Lock\LockedException($path);
+ }
}
curl_close($curl);
rewind($fp);
@@ -446,10 +449,10 @@ class DAV extends Common {
if ($e->getHttpStatus() === 501) {
return false;
}
- $this->convertException($e);
+ $this->convertException($e, $path);
return false;
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
return false;
}
} else {
@@ -502,6 +505,9 @@ class DAV extends Common {
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($statusCode !== 200) {
Util::writeLog("webdav client", 'curl GET ' . curl_getinfo($curl, CURLINFO_EFFECTIVE_URL) . ' returned status code ' . $statusCode, Util::ERROR);
+ if ($statusCode === 423) {
+ throw new \OCP\Lock\LockedException($path);
+ }
}
curl_close($curl);
fclose($source);
@@ -564,9 +570,9 @@ class DAV extends Common {
if ($e->getHttpStatus() === 404) {
return array();
}
- $this->convertException($e);
+ $this->convertException($e, $path);
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
}
return array();
}
@@ -591,9 +597,9 @@ class DAV extends Common {
if ($e->getHttpStatus() === 404) {
return false;
}
- $this->convertException($e);
+ $this->convertException($e, $path);
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
}
return false;
}
@@ -643,9 +649,9 @@ class DAV extends Common {
return false;
}
- $this->convertException($e);
+ $this->convertException($e, $path);
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
}
return false;
}
@@ -767,10 +773,10 @@ class DAV extends Common {
}
return false;
}
- $this->convertException($e);
+ $this->convertException($e, $path);
return false;
} catch (\Exception $e) {
- $this->convertException($e);
+ $this->convertException($e, $path);
return false;
}
}
@@ -782,15 +788,19 @@ class DAV extends Common {
* or do nothing.
*
* @param Exception $e sabre exception
+ * @param string $path optional path from the operation
*
* @throws StorageInvalidException if the storage is invalid, for example
* when the authentication expired or is invalid
* @throws StorageNotAvailableException if the storage is not available,
* which might be temporary
*/
- private function convertException(Exception $e) {
+ private function convertException(Exception $e, $path = '') {
Util::writeLog('files_external', $e->getMessage(), Util::ERROR);
if ($e instanceof ClientHttpException) {
+ if ($e->getHttpStatus() === 423) {
+ throw new \OCP\Lock\LockedException($path);
+ }
if ($e->getHttpStatus() === 401) {
// either password was changed or was invalid all along
throw new StorageInvalidException(get_class($e).': '.$e->getMessage());
diff --git a/lib/private/files/storage/wrapper/encryption.php b/lib/private/files/storage/wrapper/encryption.php
index 8818b822fa7..61290791faa 100644
--- a/lib/private/files/storage/wrapper/encryption.php
+++ b/lib/private/files/storage/wrapper/encryption.php
@@ -31,6 +31,7 @@ use OC\Encryption\Util;
use OC\Files\Filesystem;
use OC\Files\Mount\Manager;
use OC\Files\Storage\LocalTempFileTrait;
+use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\Encryption\IFile;
use OCP\Encryption\IManager;
use OCP\Encryption\Keys\IStorage;
@@ -174,9 +175,8 @@ class Encryption extends Wrapper {
public function file_get_contents($path) {
$encryptionModule = $this->getEncryptionModule($path);
- $info = $this->getCache()->get($path);
- if ($encryptionModule || $info['encrypted'] === true) {
+ if ($encryptionModule) {
$handle = $this->fopen($path, "r");
if (!$handle) {
return false;
@@ -338,14 +338,15 @@ class Encryption extends Wrapper {
* @param string $path
* @param string $mode
* @return resource
+ * @throws GenericEncryptionException
+ * @throws ModuleDoesNotExistsException
*/
public function fopen($path, $mode) {
$encryptionEnabled = $this->encryptionManager->isEnabled();
$shouldEncrypt = false;
$encryptionModule = null;
- $rawHeader = $this->getHeader($path);
- $header = $this->util->readHeader($rawHeader);
+ $header = $this->getHeader($path);
$fullPath = $this->getFullPath($path);
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
@@ -380,6 +381,10 @@ class Encryption extends Wrapper {
|| $mode === 'wb'
|| $mode === 'wb+'
) {
+ // don't overwrite encrypted files if encyption is not enabled
+ if ($targetIsEncrypted && $encryptionEnabled === false) {
+ throw new GenericEncryptionException('Tried to access encrypted file but encryption is not enabled');
+ }
if ($encryptionEnabled) {
// if $encryptionModuleId is empty, the default module will be used
$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
@@ -398,6 +403,7 @@ class Encryption extends Wrapper {
// OC_DEFAULT_MODULE to read the file
$encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
$shouldEncrypt = true;
+ $targetIsEncrypted = true;
}
}
} catch (ModuleDoesNotExistsException $e) {
@@ -416,7 +422,7 @@ class Encryption extends Wrapper {
$source = $this->storage->fopen($path, $mode);
$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
- $size, $unencryptedSize, strlen($rawHeader));
+ $size, $unencryptedSize, $this->getHeaderSize($path));
return $handle;
}
@@ -606,27 +612,101 @@ class Encryption extends Wrapper {
}
/**
+ * read first block of encrypted file, typically this will contain the
+ * encryption header
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function readFirstBlock($path) {
+ $firstBlock = '';
+ if ($this->storage->file_exists($path)) {
+ $handle = $this->storage->fopen($path, 'r');
+ $firstBlock = fread($handle, $this->util->getHeaderSize());
+ fclose($handle);
+ }
+ return $firstBlock;
+ }
+
+ /**
+ * return header size of given file
+ *
+ * @param string $path
+ * @return int
+ */
+ protected function getHeaderSize($path) {
+ $headerSize = 0;
+ $realFile = $this->util->stripPartialFileExtension($path);
+ if ($this->storage->file_exists($realFile)) {
+ $path = $realFile;
+ }
+ $firstBlock = $this->readFirstBlock($path);
+
+ if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
+ $headerSize = strlen($firstBlock);
+ }
+
+ return $headerSize;
+ }
+
+ /**
+ * parse raw header to array
+ *
+ * @param string $rawHeader
+ * @return array
+ */
+ protected function parseRawHeader($rawHeader) {
+ $result = array();
+ if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
+ $header = $rawHeader;
+ $endAt = strpos($header, Util::HEADER_END);
+ if ($endAt !== false) {
+ $header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
+
+ // +1 to not start with an ':' which would result in empty element at the beginning
+ $exploded = explode(':', substr($header, strlen(Util::HEADER_START)+1));
+
+ $element = array_shift($exploded);
+ while ($element !== Util::HEADER_END) {
+ $result[$element] = array_shift($exploded);
+ $element = array_shift($exploded);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
* read header from file
*
* @param string $path
* @return array
*/
protected function getHeader($path) {
- $header = '';
$realFile = $this->util->stripPartialFileExtension($path);
if ($this->storage->file_exists($realFile)) {
$path = $realFile;
}
- if ($this->storage->file_exists($path)) {
- $handle = $this->storage->fopen($path, 'r');
- $firstBlock = fread($handle, $this->util->getHeaderSize());
- fclose($handle);
- if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
- $header = $firstBlock;
+ $firstBlock = $this->readFirstBlock($path);
+ $result = $this->parseRawHeader($firstBlock);
+
+ // if the header doesn't contain a encryption module we check if it is a
+ // legacy file. If true, we add the default encryption module
+ if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
+ if (!empty($result)) {
+ $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
+ } else {
+ // if the header was empty we have to check first if it is a encrypted file at all
+ $info = $this->getCache()->get($path);
+ if (isset($info['encrypted']) && $info['encrypted'] === true) {
+ $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
+ }
}
}
- return $header;
+
+ return $result;
}
/**
@@ -639,8 +719,7 @@ class Encryption extends Wrapper {
*/
protected function getEncryptionModule($path) {
$encryptionModule = null;
- $rawHeader = $this->getHeader($path);
- $header = $this->util->readHeader($rawHeader);
+ $header = $this->getHeader($path);
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
if (!empty($encryptionModuleId)) {
try {
@@ -675,4 +754,5 @@ class Encryption extends Wrapper {
return false;
}
+
}
diff --git a/settings/application.php b/settings/application.php
index 03203b48564..a2f25935e12 100644
--- a/settings/application.php
+++ b/settings/application.php
@@ -79,7 +79,8 @@ class Application extends App {
$c->query('Config'),
$c->query('DatabaseConnection'),
$c->query('UserManager'),
- new View()
+ new View(),
+ $c->query('Logger')
);
});
$container->registerService('AppSettingsController', function(IContainer $c) {
diff --git a/settings/controller/encryptioncontroller.php b/settings/controller/encryptioncontroller.php
index 87cbf0a4bf1..7c952962c1a 100644
--- a/settings/controller/encryptioncontroller.php
+++ b/settings/controller/encryptioncontroller.php
@@ -25,6 +25,7 @@ use OC\Files\View;
use OCA\Encryption\Migration;
use OCP\IL10N;
use OCP\AppFramework\Controller;
+use OCP\ILogger;
use OCP\IRequest;
use OCP\IConfig;
use OC\DB\Connection;
@@ -50,6 +51,9 @@ class EncryptionController extends Controller {
/** @var View */
private $view;
+ /** @var ILogger */
+ private $logger;
+
/**
* @param string $appName
* @param IRequest $request
@@ -58,6 +62,7 @@ class EncryptionController extends Controller {
* @param \OC\DB\Connection $connection
* @param IUserManager $userManager
* @param View $view
+ * @param ILogger $logger
*/
public function __construct($appName,
IRequest $request,
@@ -65,7 +70,8 @@ class EncryptionController extends Controller {
IConfig $config,
Connection $connection,
IUserManager $userManager,
- View $view) {
+ View $view,
+ ILogger $logger) {
parent::__construct($appName, $request);
$this->l10n = $l10n;
$this->config = $config;
@@ -85,7 +91,7 @@ class EncryptionController extends Controller {
try {
- $migration = new Migration($this->config, $this->view, $this->connection);
+ $migration = new Migration($this->config, $this->view, $this->connection, $this->logger);
$migration->reorganizeSystemFolderStructure();
$migration->updateDB();
diff --git a/tests/lib/encryption/utiltest.php b/tests/lib/encryption/utiltest.php
index d5f5ce4c2e9..5aadb4e857f 100644
--- a/tests/lib/encryption/utiltest.php
+++ b/tests/lib/encryption/utiltest.php
@@ -75,19 +75,6 @@ class UtilTest extends TestCase {
/**
* @dataProvider providesHeaders
*/
- public function testReadHeader($header, $expected, $moduleId) {
- $expected['oc_encryption_module'] = $moduleId;
- $result = $this->util->readHeader($header);
- $this->assertSameSize($expected, $result);
- foreach ($expected as $key => $value) {
- $this->assertArrayHasKey($key, $result);
- $this->assertSame($value, $result[$key]);
- }
- }
-
- /**
- * @dataProvider providesHeaders
- */
public function testCreateHeader($expected, $header, $moduleId) {
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
diff --git a/tests/lib/files/storage/wrapper/encryption.php b/tests/lib/files/storage/wrapper/encryption.php
index a10e95a3f8b..677bbffc3d2 100644
--- a/tests/lib/files/storage/wrapper/encryption.php
+++ b/tests/lib/files/storage/wrapper/encryption.php
@@ -2,12 +2,20 @@
namespace Test\Files\Storage\Wrapper;
+use OC\Encryption\Util;
use OC\Files\Storage\Temporary;
use OC\Files\View;
class Encryption extends \Test\Files\Storage\Storage {
/**
+ * block size will always be 8192 for a PHP stream
+ * @see https://bugs.php.net/bug.php?id=21641
+ * @var integer
+ */
+ protected $headerSize = 8192;
+
+ /**
* @var Temporary
*/
private $sourceStorage;
@@ -407,18 +415,26 @@ class Encryption extends \Test\Files\Storage\Storage {
$this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager
]
)
+ ->setMethods(['readFirstBlock', 'parseRawHeader'])
->getMock();
+ $instance->expects($this->once())->method(('parseRawHeader'))
+ ->willReturn([Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']);
+
+ if ($strippedPathExists) {
+ $instance->expects($this->once())->method('readFirstBlock')
+ ->with($strippedPath)->willReturn('');
+ } else {
+ $instance->expects($this->once())->method('readFirstBlock')
+ ->with($path)->willReturn('');
+ }
+
$util->expects($this->once())->method('stripPartialFileExtension')
->with($path)->willReturn($strippedPath);
- $sourceStorage->expects($this->at(0))
+ $sourceStorage->expects($this->once())
->method('file_exists')
->with($strippedPath)
->willReturn($strippedPathExists);
- $sourceStorage->expects($this->at(1))
- ->method('file_exists')
- ->with($strippedPathExists ? $strippedPath : $path)
- ->willReturn(false);
$this->invokePrivate($instance, 'getHeader', [$path]);
}
@@ -432,4 +448,98 @@ class Encryption extends \Test\Files\Storage\Storage {
array('/foo/bar.txt.ocTransferId7437493.part', true, '/foo/bar.txt'),
);
}
+
+ /**
+ * test if getHeader adds the default module correctly to the header for
+ * legacy files
+ *
+ * @dataProvider dataTestGetHeaderAddLegacyModule
+ */
+ public function testGetHeaderAddLegacyModule($header, $isEncrypted, $expected) {
+
+ $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()->getMock();
+
+ $util = $this->getMockBuilder('\OC\Encryption\Util')
+ ->setConstructorArgs([new View(), new \OC\User\Manager(), $this->groupManager, $this->config])
+ ->getMock();
+
+ $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+ $cache->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function($path) use ($isEncrypted) {return ['encrypted' => $isEncrypted, 'path' => $path];});
+
+ $instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption')
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager
+ ]
+ )
+ ->setMethods(['readFirstBlock', 'parseRawHeader', 'getCache'])
+ ->getMock();
+
+ $instance->expects($this->once())->method(('parseRawHeader'))->willReturn($header);
+ $instance->expects($this->any())->method('getCache')->willReturn($cache);
+
+ $result = $this->invokePrivate($instance, 'getHeader', ['test.txt']);
+ $this->assertSameSize($expected, $result);
+ foreach ($result as $key => $value) {
+ $this->assertArrayHasKey($key, $expected);
+ $this->assertSame($expected[$key], $value);
+ }
+ }
+
+ public function dataTestGetHeaderAddLegacyModule() {
+ return [
+ [['cipher' => 'AES-128'], true, ['cipher' => 'AES-128', Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
+ [[], true, [Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
+ [[], false, []],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestParseRawHeader
+ */
+ public function testParseRawHeader($rawHeader, $expected) {
+ $instance = new \OC\Files\Storage\Wrapper\Encryption(
+ [
+ 'storage' => $this->sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager
+
+ );
+
+ $result = $this->invokePrivate($instance, 'parseRawHeader', [$rawHeader]);
+ $this->assertSameSize($expected, $result);
+ foreach ($result as $key => $value) {
+ $this->assertArrayHasKey($key, $expected);
+ $this->assertSame($expected[$key], $value);
+ }
+ }
+
+ public function dataTestParseRawHeader() {
+ return [
+ [str_pad('HBEGIN:oc_encryption_module:0:HEND', $this->headerSize, '-', STR_PAD_RIGHT)
+ , [Util::HEADER_ENCRYPTION_MODULE_KEY => '0']],
+ [str_pad('HBEGIN:oc_encryption_module:0:custom_header:foo:HEND', $this->headerSize, '-', STR_PAD_RIGHT)
+ , ['custom_header' => 'foo', Util::HEADER_ENCRYPTION_MODULE_KEY => '0']],
+ [str_pad('HelloWorld', $this->headerSize, '-', STR_PAD_RIGHT), []],
+ ['', []],
+ [str_pad('HBEGIN:oc_encryption_module:0', $this->headerSize, '-', STR_PAD_RIGHT)
+ , []],
+ [str_pad('oc_encryption_module:0:HEND', $this->headerSize, '-', STR_PAD_RIGHT)
+ , []],
+ ];
+ }
+
}