diff options
-rw-r--r-- | apps/encryption/appinfo/register_command.php | 3 | ||||
-rw-r--r-- | apps/encryption/command/migratekeys.php | 10 | ||||
-rw-r--r-- | apps/encryption/lib/migration.php | 74 | ||||
-rw-r--r-- | apps/encryption/tests/lib/MigrationTest.php | 49 | ||||
-rw-r--r-- | lib/private/app.php | 4 | ||||
-rw-r--r-- | lib/private/encryption/util.php | 29 | ||||
-rw-r--r-- | lib/private/files/storage/dav.php | 44 | ||||
-rw-r--r-- | lib/private/files/storage/wrapper/encryption.php | 110 | ||||
-rw-r--r-- | settings/application.php | 3 | ||||
-rw-r--r-- | settings/controller/encryptioncontroller.php | 10 | ||||
-rw-r--r-- | tests/lib/encryption/utiltest.php | 13 | ||||
-rw-r--r-- | tests/lib/files/storage/wrapper/encryption.php | 120 |
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) + , []], + ]; + } + } |