diff options
author | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-22 17:34:01 +0200 |
---|---|---|
committer | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-22 17:34:01 +0200 |
commit | d379157289d2e09f41b10253d90ed0867e267a7e (patch) | |
tree | 8007eb9cdc0b8ef4860159d4ee15c06375d49623 /lib/private/Encryption | |
parent | 8c7c713e86ed144b1a5e80ea27fdba4744b6d09d (diff) | |
download | nextcloud-server-d379157289d2e09f41b10253d90ed0867e267a7e.tar.gz nextcloud-server-d379157289d2e09f41b10253d90ed0867e267a7e.zip |
Move \OC\Encryption to PSR-4
Diffstat (limited to 'lib/private/Encryption')
16 files changed, 2017 insertions, 0 deletions
diff --git a/lib/private/Encryption/DecryptAll.php b/lib/private/Encryption/DecryptAll.php new file mode 100644 index 00000000000..7a965a5f227 --- /dev/null +++ b/lib/private/Encryption/DecryptAll.php @@ -0,0 +1,276 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Encryption; + +use OC\Encryption\Exceptions\DecryptionFailedException; +use OC\Files\View; +use \OCP\Encryption\IEncryptionModule; +use OCP\IUserManager; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class DecryptAll { + + /** @var OutputInterface */ + protected $output; + + /** @var InputInterface */ + protected $input; + + /** @var Manager */ + protected $encryptionManager; + + /** @var IUserManager */ + protected $userManager; + + /** @var View */ + protected $rootView; + + /** @var array files which couldn't be decrypted */ + protected $failed; + + /** + * @param Manager $encryptionManager + * @param IUserManager $userManager + * @param View $rootView + */ + public function __construct( + Manager $encryptionManager, + IUserManager $userManager, + View $rootView + ) { + $this->encryptionManager = $encryptionManager; + $this->userManager = $userManager; + $this->rootView = $rootView; + $this->failed = []; + } + + /** + * start to decrypt all files + * + * @param InputInterface $input + * @param OutputInterface $output + * @param string $user which users data folder should be decrypted, default = all users + * @return bool + * @throws \Exception + */ + public function decryptAll(InputInterface $input, OutputInterface $output, $user = '') { + + $this->input = $input; + $this->output = $output; + + if (!empty($user) && $this->userManager->userExists($user) === false) { + $this->output->writeln('User "' . $user . '" does not exist. Please check the username and try again'); + return false; + } + + $this->output->writeln('prepare encryption modules...'); + if ($this->prepareEncryptionModules($user) === false) { + return false; + } + $this->output->writeln(' done.'); + + $this->decryptAllUsersFiles($user); + + if (empty($this->failed)) { + $this->output->writeln('all files could be decrypted successfully!'); + } else { + $this->output->writeln('Files for following users couldn\'t be decrypted, '); + $this->output->writeln('maybe the user is not set up in a way that supports this operation: '); + foreach ($this->failed as $uid => $paths) { + $this->output->writeln(' ' . $uid); + } + $this->output->writeln(''); + } + + return true; + } + + /** + * prepare encryption modules to perform the decrypt all function + * + * @param $user + * @return bool + */ + protected function prepareEncryptionModules($user) { + // prepare all encryption modules for decrypt all + $encryptionModules = $this->encryptionManager->getEncryptionModules(); + foreach ($encryptionModules as $moduleDesc) { + /** @var IEncryptionModule $module */ + $module = call_user_func($moduleDesc['callback']); + $this->output->writeln(''); + $this->output->writeln('Prepare "' . $module->getDisplayName() . '"'); + $this->output->writeln(''); + if ($module->prepareDecryptAll($this->input, $this->output, $user) === false) { + $this->output->writeln('Module "' . $moduleDesc['displayName'] . '" does not support the functionality to decrypt all files again or the initialization of the module failed!'); + return false; + } + } + + return true; + } + + /** + * iterate over all user and encrypt their files + * @param string $user which users files should be decrypted, default = all users + */ + protected function decryptAllUsersFiles($user = '') { + + $this->output->writeln("\n"); + + $userList = []; + if (empty($user)) { + + $fetchUsersProgress = new ProgressBar($this->output); + $fetchUsersProgress->setFormat(" %message% \n [%bar%]"); + $fetchUsersProgress->start(); + $fetchUsersProgress->setMessage("Fetch list of users..."); + $fetchUsersProgress->advance(); + + foreach ($this->userManager->getBackends() as $backend) { + $limit = 500; + $offset = 0; + do { + $users = $backend->getUsers('', $limit, $offset); + foreach ($users as $user) { + $userList[] = $user; + } + $offset += $limit; + $fetchUsersProgress->advance(); + } while (count($users) >= $limit); + $fetchUsersProgress->setMessage("Fetch list of users... finished"); + $fetchUsersProgress->finish(); + } + } else { + $userList[] = $user; + } + + $this->output->writeln("\n\n"); + + $progress = new ProgressBar($this->output); + $progress->setFormat(" %message% \n [%bar%]"); + $progress->start(); + $progress->setMessage("starting to decrypt files..."); + $progress->advance(); + + $numberOfUsers = count($userList); + $userNo = 1; + foreach ($userList as $uid) { + $userCount = "$uid ($userNo of $numberOfUsers)"; + $this->decryptUsersFiles($uid, $progress, $userCount); + $userNo++; + } + + $progress->setMessage("starting to decrypt files... finished"); + $progress->finish(); + + $this->output->writeln("\n\n"); + + } + + /** + * encrypt files from the given user + * + * @param string $uid + * @param ProgressBar $progress + * @param string $userCount + */ + protected function decryptUsersFiles($uid, ProgressBar $progress, $userCount) { + + $this->setupUserFS($uid); + $directories = array(); + $directories[] = '/' . $uid . '/files'; + + while($root = array_pop($directories)) { + $content = $this->rootView->getDirectoryContent($root); + foreach ($content as $file) { + $path = $root . '/' . $file['name']; + if ($this->rootView->is_dir($path)) { + $directories[] = $path; + continue; + } else { + try { + $progress->setMessage("decrypt files for user $userCount: $path"); + $progress->advance(); + if ($this->decryptFile($path) === false) { + $progress->setMessage("decrypt files for user $userCount: $path (already decrypted)"); + $progress->advance(); + } + } catch (\Exception $e) { + if (isset($this->failed[$uid])) { + $this->failed[$uid][] = $path; + } else { + $this->failed[$uid] = [$path]; + } + } + } + } + } + } + + /** + * encrypt file + * + * @param string $path + * @return bool + */ + protected function decryptFile($path) { + + $source = $path; + $target = $path . '.decrypted.' . $this->getTimestamp(); + + try { + $this->rootView->copy($source, $target); + $this->rootView->rename($target, $source); + } catch (DecryptionFailedException $e) { + if ($this->rootView->file_exists($target)) { + $this->rootView->unlink($target); + } + return false; + } + + return true; + } + + /** + * get current timestamp + * + * @return int + */ + protected function getTimestamp() { + return time(); + } + + + /** + * setup user file system + * + * @param string $uid + */ + protected function setupUserFS($uid) { + \OC_Util::tearDownFS(); + \OC_Util::setupFS($uid); + } + +} diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php new file mode 100644 index 00000000000..11beb0cd6b1 --- /dev/null +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -0,0 +1,124 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Encryption; + + +use OC\Memcache\ArrayCache; +use OC\Files\Filesystem; +use OC\Files\Storage\Wrapper\Encryption; +use OCP\Files\Mount\IMountPoint; +use OC\Files\View; +use OCP\Files\Storage; +use OCP\ILogger; + +/** + * Class EncryptionWrapper + * + * applies the encryption storage wrapper + * + * @package OC\Encryption + */ +class EncryptionWrapper { + + /** @var ArrayCache */ + private $arrayCache; + + /** @var Manager */ + private $manager; + + /** @var ILogger */ + private $logger; + + /** + * EncryptionWrapper constructor. + * + * @param ArrayCache $arrayCache + * @param Manager $manager + * @param ILogger $logger + */ + public function __construct(ArrayCache $arrayCache, + Manager $manager, + ILogger $logger + ) { + $this->arrayCache = $arrayCache; + $this->manager = $manager; + $this->logger = $logger; + } + + /** + * Wraps the given storage when it is not a shared storage + * + * @param string $mountPoint + * @param Storage $storage + * @param IMountPoint $mount + * @return Encryption|Storage + */ + public function wrapStorage($mountPoint, Storage $storage, IMountPoint $mount) { + $parameters = [ + 'storage' => $storage, + 'mountPoint' => $mountPoint, + 'mount' => $mount + ]; + + if (!$storage->instanceOfStorage('OC\Files\Storage\Shared') + && !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') + && !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud')) { + + $user = \OC::$server->getUserSession()->getUser(); + $mountManager = Filesystem::getMountManager(); + $uid = $user ? $user->getUID() : null; + $fileHelper = \OC::$server->getEncryptionFilesHelper(); + $keyStorage = \OC::$server->getEncryptionKeyStorage(); + + $util = new Util( + new View(), + \OC::$server->getUserManager(), + \OC::$server->getGroupManager(), + \OC::$server->getConfig() + ); + $update = new Update( + new View(), + $util, + Filesystem::getMountManager(), + $this->manager, + $fileHelper, + $uid + ); + return new Encryption( + $parameters, + $this->manager, + $util, + $this->logger, + $fileHelper, + $uid, + $keyStorage, + $update, + $mountManager, + $this->arrayCache + ); + } else { + return $storage; + } + } + +} diff --git a/lib/private/Encryption/Exceptions/DecryptionFailedException.php b/lib/private/Encryption/Exceptions/DecryptionFailedException.php new file mode 100644 index 00000000000..a0cbbc5cce0 --- /dev/null +++ b/lib/private/Encryption/Exceptions/DecryptionFailedException.php @@ -0,0 +1,30 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class DecryptionFailedException extends GenericEncryptionException { + +} diff --git a/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php b/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php new file mode 100644 index 00000000000..2c90c2db7df --- /dev/null +++ b/lib/private/Encryption/Exceptions/EmptyEncryptionDataException.php @@ -0,0 +1,30 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EmptyEncryptionDataException extends GenericEncryptionException{ + +} diff --git a/lib/private/Encryption/Exceptions/EncryptionFailedException.php b/lib/private/Encryption/Exceptions/EncryptionFailedException.php new file mode 100644 index 00000000000..98e92eb199c --- /dev/null +++ b/lib/private/Encryption/Exceptions/EncryptionFailedException.php @@ -0,0 +1,30 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EncryptionFailedException extends GenericEncryptionException{ + +} diff --git a/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php b/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php new file mode 100644 index 00000000000..ab1a166018c --- /dev/null +++ b/lib/private/Encryption/Exceptions/EncryptionHeaderKeyExistsException.php @@ -0,0 +1,35 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EncryptionHeaderKeyExistsException extends GenericEncryptionException { + + /** + * @param string $key + */ + public function __construct($key) { + parent::__construct('header key "'. $key . '" already reserved by ownCloud'); + } +} diff --git a/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php b/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php new file mode 100644 index 00000000000..7b706e621de --- /dev/null +++ b/lib/private/Encryption/Exceptions/EncryptionHeaderToLargeException.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class EncryptionHeaderToLargeException extends GenericEncryptionException { + + public function __construct() { + parent::__construct('max header size exceeded'); + } + +} diff --git a/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php b/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php new file mode 100644 index 00000000000..fcd08679acc --- /dev/null +++ b/lib/private/Encryption/Exceptions/ModuleAlreadyExistsException.php @@ -0,0 +1,37 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class ModuleAlreadyExistsException extends GenericEncryptionException { + + /** + * @param string $id + * @param string $name + */ + public function __construct($id, $name) { + parent::__construct('Id "' . $id . '" already used by encryption module "' . $name . '"'); + } + +} diff --git a/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php b/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php new file mode 100644 index 00000000000..282c9ec080b --- /dev/null +++ b/lib/private/Encryption/Exceptions/ModuleDoesNotExistsException.php @@ -0,0 +1,29 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class ModuleDoesNotExistsException extends GenericEncryptionException { + +} diff --git a/lib/private/Encryption/Exceptions/UnknownCipherException.php b/lib/private/Encryption/Exceptions/UnknownCipherException.php new file mode 100644 index 00000000000..beb4cb7f2e5 --- /dev/null +++ b/lib/private/Encryption/Exceptions/UnknownCipherException.php @@ -0,0 +1,30 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OC\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class UnknownCipherException extends GenericEncryptionException { + +} diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php new file mode 100644 index 00000000000..ec55c2cea00 --- /dev/null +++ b/lib/private/Encryption/File.php @@ -0,0 +1,99 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption; + +class File implements \OCP\Encryption\IFile { + + /** @var Util */ + protected $util; + + /** + * cache results of already checked folders + * + * @var array + */ + protected $cache; + + public function __construct(Util $util) { + $this->util = $util; + } + + + /** + * get list of users with access to the file + * + * @param string $path to the file + * @return array ['users' => $uniqueUserIds, 'public' => $public] + */ + public function getAccessList($path) { + + // Make sure that a share key is generated for the owner too + list($owner, $ownerPath) = $this->util->getUidAndFilename($path); + + // always add owner to the list of users with access to the file + $userIds = array($owner); + + if (!$this->util->isFile($owner . '/' . $ownerPath)) { + return array('users' => $userIds, 'public' => false); + } + + $ownerPath = substr($ownerPath, strlen('/files')); + $ownerPath = $this->util->stripPartialFileExtension($ownerPath); + + + // first get the shares for the parent and cache the result so that we don't + // need to check all parents for every file + $parent = dirname($ownerPath); + if (isset($this->cache[$parent])) { + $resultForParents = $this->cache[$parent]; + } else { + $resultForParents = \OCP\Share::getUsersSharingFile($parent, $owner); + $this->cache[$parent] = $resultForParents; + } + $userIds = \array_merge($userIds, $resultForParents['users']); + $public = $resultForParents['public'] || $resultForParents['remote']; + + + // Find out who, if anyone, is sharing the file + $resultForFile = \OCP\Share::getUsersSharingFile($ownerPath, $owner, false, false, false); + $userIds = \array_merge($userIds, $resultForFile['users']); + $public = $resultForFile['public'] || $resultForFile['remote'] || $public; + + // check if it is a group mount + if (\OCP\App::isEnabled("files_external")) { + $mounts = \OC_Mount_Config::getSystemMountPoints(); + foreach ($mounts as $mount) { + if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) { + $mountedFor = $this->util->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups']); + $userIds = array_merge($userIds, $mountedFor); + } + } + } + + // Remove duplicate UIDs + $uniqueUserIds = array_unique($userIds); + + return array('users' => $uniqueUserIds, 'public' => $public); + } + +} diff --git a/lib/private/Encryption/HookManager.php b/lib/private/Encryption/HookManager.php new file mode 100644 index 00000000000..0bc42ec8159 --- /dev/null +++ b/lib/private/Encryption/HookManager.php @@ -0,0 +1,75 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption; + +use OC\Files\Filesystem; +use OC\Files\View; + +class HookManager { + /** + * @var Update + */ + private static $updater; + + public static function postShared($params) { + self::getUpdate()->postShared($params); + } + public static function postUnshared($params) { + self::getUpdate()->postUnshared($params); + } + + public static function postRename($params) { + self::getUpdate()->postRename($params); + } + + public static function postRestore($params) { + self::getUpdate()->postRestore($params); + } + + /** + * @return Update + */ + private static function getUpdate() { + if (is_null(self::$updater)) { + $user = \OC::$server->getUserSession()->getUser(); + $uid = ''; + if ($user) { + $uid = $user->getUID(); + } + self::$updater = new Update( + new View(), + new Util( + new View(), + \OC::$server->getUserManager(), + \OC::$server->getGroupManager(), + \OC::$server->getConfig()), + Filesystem::getMountManager(), + \OC::$server->getEncryptionManager(), + \OC::$server->getEncryptionFilesHelper(), + $uid + ); + } + + return self::$updater; + } +} diff --git a/lib/private/Encryption/Keys/Storage.php b/lib/private/Encryption/Keys/Storage.php new file mode 100644 index 00000000000..47360f45aa5 --- /dev/null +++ b/lib/private/Encryption/Keys/Storage.php @@ -0,0 +1,326 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption\Keys; + +use OC\Encryption\Util; +use OC\Files\Filesystem; +use OC\Files\View; +use OCP\Encryption\Keys\IStorage; + +class Storage implements IStorage { + + // hidden file which indicate that the folder is a valid key storage + const KEY_STORAGE_MARKER = '.oc_key_storage'; + + /** @var View */ + private $view; + + /** @var Util */ + private $util; + + // base dir where all the file related keys are stored + /** @var string */ + private $keys_base_dir; + + // root of the key storage default is empty which means that we use the data folder + /** @var string */ + private $root_dir; + + /** @var string */ + private $encryption_base_dir; + + /** @var array */ + private $keyCache = []; + + /** + * @param View $view + * @param Util $util + */ + public function __construct(View $view, Util $util) { + $this->view = $view; + $this->util = $util; + + $this->encryption_base_dir = '/files_encryption'; + $this->keys_base_dir = $this->encryption_base_dir .'/keys'; + $this->root_dir = $this->util->getKeyStorageRoot(); + } + + /** + * @inheritdoc + */ + public function getUserKey($uid, $keyId, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); + return $this->getKey($path); + } + + /** + * @inheritdoc + */ + public function getFileKey($path, $keyId, $encryptionModuleId) { + $realFile = $this->util->stripPartialFileExtension($path); + $keyDir = $this->getFileKeyDir($encryptionModuleId, $realFile); + $key = $this->getKey($keyDir . $keyId); + + if ($key === '' && $realFile !== $path) { + // Check if the part file has keys and use them, if no normal keys + // exist. This is required to fix copyBetweenStorage() when we + // rename a .part file over storage borders. + $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + $key = $this->getKey($keyDir . $keyId); + } + + return $key; + } + + /** + * @inheritdoc + */ + public function getSystemUserKey($keyId, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); + return $this->getKey($path); + } + + /** + * @inheritdoc + */ + public function setUserKey($uid, $keyId, $key, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); + return $this->setKey($path, $key); + } + + /** + * @inheritdoc + */ + public function setFileKey($path, $keyId, $key, $encryptionModuleId) { + $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + return $this->setKey($keyDir . $keyId, $key); + } + + /** + * @inheritdoc + */ + public function setSystemUserKey($keyId, $key, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); + return $this->setKey($path, $key); + } + + /** + * @inheritdoc + */ + public function deleteUserKey($uid, $keyId, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, $uid); + return !$this->view->file_exists($path) || $this->view->unlink($path); + } + + /** + * @inheritdoc + */ + public function deleteFileKey($path, $keyId, $encryptionModuleId) { + $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + return !$this->view->file_exists($keyDir . $keyId) || $this->view->unlink($keyDir . $keyId); + } + + /** + * @inheritdoc + */ + public function deleteAllFileKeys($path) { + $keyDir = $this->getFileKeyDir('', $path); + return !$this->view->file_exists($keyDir) || $this->view->deleteAll($keyDir); + } + + /** + * @inheritdoc + */ + public function deleteSystemUserKey($keyId, $encryptionModuleId) { + $path = $this->constructUserKeyPath($encryptionModuleId, $keyId, null); + return !$this->view->file_exists($path) || $this->view->unlink($path); + } + + /** + * construct path to users key + * + * @param string $encryptionModuleId + * @param string $keyId + * @param string $uid + * @return string + */ + protected function constructUserKeyPath($encryptionModuleId, $keyId, $uid) { + + if ($uid === null) { + $path = $this->root_dir . '/' . $this->encryption_base_dir . '/' . $encryptionModuleId . '/' . $keyId; + } else { + $path = $this->root_dir . '/' . $uid . $this->encryption_base_dir . '/' + . $encryptionModuleId . '/' . $uid . '.' . $keyId; + } + + return \OC\Files\Filesystem::normalizePath($path); + } + + /** + * read key from hard disk + * + * @param string $path to key + * @return string + */ + private function getKey($path) { + + $key = ''; + + if ($this->view->file_exists($path)) { + if (isset($this->keyCache[$path])) { + $key = $this->keyCache[$path]; + } else { + $key = $this->view->file_get_contents($path); + $this->keyCache[$path] = $key; + } + } + + return $key; + } + + /** + * write key to disk + * + * + * @param string $path path to key directory + * @param string $key key + * @return bool + */ + private function setKey($path, $key) { + $this->keySetPreparation(dirname($path)); + + $result = $this->view->file_put_contents($path, $key); + + if (is_int($result) && $result > 0) { + $this->keyCache[$path] = $key; + return true; + } + + return false; + } + + /** + * get path to key folder for a given file + * + * @param string $encryptionModuleId + * @param string $path path to the file, relative to data/ + * @return string + */ + private function getFileKeyDir($encryptionModuleId, $path) { + + list($owner, $filename) = $this->util->getUidAndFilename($path); + + // in case of system wide mount points the keys are stored directly in the data directory + if ($this->util->isSystemWideMountPoint($filename, $owner)) { + $keyPath = $this->root_dir . '/' . $this->keys_base_dir . $filename . '/'; + } else { + $keyPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $filename . '/'; + } + + return Filesystem::normalizePath($keyPath . $encryptionModuleId . '/', false); + } + + /** + * move keys if a file was renamed + * + * @param string $source + * @param string $target + * @return boolean + */ + public function renameKeys($source, $target) { + + $sourcePath = $this->getPathToKeys($source); + $targetPath = $this->getPathToKeys($target); + + if ($this->view->file_exists($sourcePath)) { + $this->keySetPreparation(dirname($targetPath)); + $this->view->rename($sourcePath, $targetPath); + + return true; + } + + return false; + } + + + /** + * copy keys if a file was renamed + * + * @param string $source + * @param string $target + * @return boolean + */ + public function copyKeys($source, $target) { + + $sourcePath = $this->getPathToKeys($source); + $targetPath = $this->getPathToKeys($target); + + if ($this->view->file_exists($sourcePath)) { + $this->keySetPreparation(dirname($targetPath)); + $this->view->copy($sourcePath, $targetPath); + return true; + } + + return false; + } + + /** + * get system wide path and detect mount points + * + * @param string $path + * @return string + */ + protected function getPathToKeys($path) { + list($owner, $relativePath) = $this->util->getUidAndFilename($path); + $systemWideMountPoint = $this->util->isSystemWideMountPoint($relativePath, $owner); + + if ($systemWideMountPoint) { + $systemPath = $this->root_dir . '/' . $this->keys_base_dir . $relativePath . '/'; + } else { + $systemPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $relativePath . '/'; + } + + return Filesystem::normalizePath($systemPath, false); + } + + /** + * Make preparations to filesystem for saving a key file + * + * @param string $path relative to the views root + */ + protected function keySetPreparation($path) { + // If the file resides within a subdirectory, create it + if (!$this->view->file_exists($path)) { + $sub_dirs = explode('/', ltrim($path, '/')); + $dir = ''; + foreach ($sub_dirs as $sub_dir) { + $dir .= '/' . $sub_dir; + if (!$this->view->is_dir($dir)) { + $this->view->mkdir($dir); + } + } + } + } + +} diff --git a/lib/private/Encryption/Manager.php b/lib/private/Encryption/Manager.php new file mode 100644 index 00000000000..8714d161807 --- /dev/null +++ b/lib/private/Encryption/Manager.php @@ -0,0 +1,284 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Jan-Christoph Borchardt <hey@jancborchardt.net> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption; + +use OC\Encryption\Keys\Storage; +use OC\Files\Filesystem; +use OC\Files\View; +use OC\Memcache\ArrayCache; +use OC\ServiceUnavailableException; +use OCP\Encryption\IEncryptionModule; +use OCP\Encryption\IManager; +use OCP\IConfig; +use OCP\IL10N; +use OCP\ILogger; + +class Manager implements IManager { + + /** @var array */ + protected $encryptionModules; + + /** @var IConfig */ + protected $config; + + /** @var ILogger */ + protected $logger; + + /** @var Il10n */ + protected $l; + + /** @var View */ + protected $rootView; + + /** @var Util */ + protected $util; + + /** @var ArrayCache */ + protected $arrayCache; + + /** + * @param IConfig $config + * @param ILogger $logger + * @param IL10N $l10n + * @param View $rootView + * @param Util $util + * @param ArrayCache $arrayCache + */ + public function __construct(IConfig $config, ILogger $logger, IL10N $l10n, View $rootView, Util $util, ArrayCache $arrayCache) { + $this->encryptionModules = array(); + $this->config = $config; + $this->logger = $logger; + $this->l = $l10n; + $this->rootView = $rootView; + $this->util = $util; + $this->arrayCache = $arrayCache; + } + + /** + * Check if encryption is enabled + * + * @return bool true if enabled, false if not + */ + public function isEnabled() { + + $installed = $this->config->getSystemValue('installed', false); + if (!$installed) { + return false; + } + + $enabled = $this->config->getAppValue('core', 'encryption_enabled', 'no'); + return $enabled === 'yes'; + } + + /** + * check if new encryption is ready + * + * @return bool + * @throws ServiceUnavailableException + */ + 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 "Default encryption module" + and run \'occ encryption:migrate\''; + $this->logger->warning($warning); + return false; + } + + if ($this->isKeyStorageReady() === false) { + throw new ServiceUnavailableException('Key Storage is not ready'); + } + + return true; + } + + /** + * @param string $user + */ + public function isReadyForUser($user) { + if (!$this->isReady()) { + return false; + } + + foreach ($this->getEncryptionModules() as $module) { + /** @var IEncryptionModule $m */ + $m = call_user_func($module['callback']); + if (!$m->isReadyForUser($user)) { + return false; + } + } + + return true; + } + + /** + * Registers an callback function which must return an encryption module instance + * + * @param string $id + * @param string $displayName + * @param callable $callback + * @throws Exceptions\ModuleAlreadyExistsException + */ + public function registerEncryptionModule($id, $displayName, callable $callback) { + + if (isset($this->encryptionModules[$id])) { + throw new Exceptions\ModuleAlreadyExistsException($id, $displayName); + } + + $this->encryptionModules[$id] = [ + 'id' => $id, + 'displayName' => $displayName, + 'callback' => $callback, + ]; + + $defaultEncryptionModuleId = $this->getDefaultEncryptionModuleId(); + + if (empty($defaultEncryptionModuleId)) { + $this->setDefaultEncryptionModule($id); + } + } + + /** + * Unregisters an encryption module + * + * @param string $moduleId + */ + public function unregisterEncryptionModule($moduleId) { + unset($this->encryptionModules[$moduleId]); + } + + /** + * get a list of all encryption modules + * + * @return array [id => ['id' => $id, 'displayName' => $displayName, 'callback' => callback]] + */ + public function getEncryptionModules() { + return $this->encryptionModules; + } + + /** + * get a specific encryption module + * + * @param string $moduleId + * @return IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function getEncryptionModule($moduleId = '') { + if (!empty($moduleId)) { + if (isset($this->encryptionModules[$moduleId])) { + return call_user_func($this->encryptionModules[$moduleId]['callback']); + } else { + $message = "Module with id: $moduleId does not exist."; + $hint = $this->l->t('Module with id: %s does not exist. Please enable it in your apps settings or contact your administrator.', [$moduleId]); + throw new Exceptions\ModuleDoesNotExistsException($message, $hint); + } + } else { + return $this->getDefaultEncryptionModule(); + } + } + + /** + * get default encryption module + * + * @return \OCP\Encryption\IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + protected function getDefaultEncryptionModule() { + $defaultModuleId = $this->getDefaultEncryptionModuleId(); + if (!empty($defaultModuleId)) { + if (isset($this->encryptionModules[$defaultModuleId])) { + return call_user_func($this->encryptionModules[$defaultModuleId]['callback']); + } else { + $message = 'Default encryption module not loaded'; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + } else { + $message = 'No default encryption module defined'; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + + } + + /** + * set default encryption module Id + * + * @param string $moduleId + * @return bool + */ + public function setDefaultEncryptionModule($moduleId) { + try { + $this->getEncryptionModule($moduleId); + } catch (\Exception $e) { + return false; + } + + $this->config->setAppValue('core', 'default_encryption_module', $moduleId); + return true; + } + + /** + * get default encryption module Id + * + * @return string + */ + public function getDefaultEncryptionModuleId() { + return $this->config->getAppValue('core', 'default_encryption_module'); + } + + /** + * Add storage wrapper + */ + public function setupStorage() { + $encryptionWrapper = new EncryptionWrapper($this->arrayCache, $this, $this->logger); + Filesystem::addStorageWrapper('oc_encryption', array($encryptionWrapper, 'wrapStorage'), 2); + } + + + /** + * check if key storage is ready + * + * @return bool + */ + protected function isKeyStorageReady() { + + $rootDir = $this->util->getKeyStorageRoot(); + + // the default root is always valid + if ($rootDir === '') { + return true; + } + + // check if key storage is mounted correctly + if ($this->rootView->file_exists($rootDir . '/' . Storage::KEY_STORAGE_MARKER)) { + return true; + } + + return false; + } + + +} diff --git a/lib/private/Encryption/Update.php b/lib/private/Encryption/Update.php new file mode 100644 index 00000000000..62c23c1fe0c --- /dev/null +++ b/lib/private/Encryption/Update.php @@ -0,0 +1,185 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption; + +use OC\Files\Filesystem; +use \OC\Files\Mount; +use \OC\Files\View; + +/** + * update encrypted files, e.g. because a file was shared + */ +class Update { + + /** @var \OC\Files\View */ + protected $view; + + /** @var \OC\Encryption\Util */ + protected $util; + + /** @var \OC\Files\Mount\Manager */ + protected $mountManager; + + /** @var \OC\Encryption\Manager */ + protected $encryptionManager; + + /** @var string */ + protected $uid; + + /** @var \OC\Encryption\File */ + protected $file; + + /** + * + * @param \OC\Files\View $view + * @param \OC\Encryption\Util $util + * @param \OC\Files\Mount\Manager $mountManager + * @param \OC\Encryption\Manager $encryptionManager + * @param \OC\Encryption\File $file + * @param string $uid + */ + public function __construct( + View $view, + Util $util, + Mount\Manager $mountManager, + Manager $encryptionManager, + File $file, + $uid + ) { + + $this->view = $view; + $this->util = $util; + $this->mountManager = $mountManager; + $this->encryptionManager = $encryptionManager; + $this->file = $file; + $this->uid = $uid; + } + + /** + * hook after file was shared + * + * @param array $params + */ + public function postShared($params) { + if ($this->encryptionManager->isEnabled()) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $path = Filesystem::getPath($params['fileSource']); + list($owner, $ownerPath) = $this->getOwnerPath($path); + $absPath = '/' . $owner . '/files/' . $ownerPath; + $this->update($absPath); + } + } + } + + /** + * hook after file was unshared + * + * @param array $params + */ + public function postUnshared($params) { + if ($this->encryptionManager->isEnabled()) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $path = Filesystem::getPath($params['fileSource']); + list($owner, $ownerPath) = $this->getOwnerPath($path); + $absPath = '/' . $owner . '/files/' . $ownerPath; + $this->update($absPath); + } + } + } + + /** + * inform encryption module that a file was restored from the trash bin, + * e.g. to update the encryption keys + * + * @param array $params + */ + public function postRestore($params) { + if ($this->encryptionManager->isEnabled()) { + $path = Filesystem::normalizePath('/' . $this->uid . '/files/' . $params['filePath']); + $this->update($path); + } + } + + /** + * inform encryption module that a file was renamed, + * e.g. to update the encryption keys + * + * @param array $params + */ + public function postRename($params) { + $source = $params['oldpath']; + $target = $params['newpath']; + if( + $this->encryptionManager->isEnabled() && + dirname($source) !== dirname($target) + ) { + list($owner, $ownerPath) = $this->getOwnerPath($target); + $absPath = '/' . $owner . '/files/' . $ownerPath; + $this->update($absPath); + } + } + + /** + * get owner and path relative to data/<owner>/files + * + * @param string $path path to file for current user + * @return array ['owner' => $owner, 'path' => $path] + * @throw \InvalidArgumentException + */ + protected function getOwnerPath($path) { + $info = Filesystem::getFileInfo($path); + $owner = Filesystem::getOwner($path); + $view = new View('/' . $owner . '/files'); + $path = $view->getPath($info->getId()); + if ($path === null) { + throw new \InvalidArgumentException('No file found for ' . $info->getId()); + } + + return array($owner, $path); + } + + /** + * notify encryption module about added/removed users from a file/folder + * + * @param string $path relative to data/ + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function update($path) { + + // if a folder was shared, get a list of all (sub-)folders + if ($this->view->is_dir($path)) { + $allFiles = $this->util->getAllFiles($path); + } else { + $allFiles = array($path); + } + + $encryptionModule = $this->encryptionManager->getEncryptionModule(); + + foreach ($allFiles as $file) { + $usersSharing = $this->file->getAccessList($file); + $encryptionModule->update($file, $this->uid, $usersSharing); + } + } + +} diff --git a/lib/private/Encryption/Util.php b/lib/private/Encryption/Util.php new file mode 100644 index 00000000000..9e0cfca830d --- /dev/null +++ b/lib/private/Encryption/Util.php @@ -0,0 +1,393 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Jan-Christoph Borchardt <hey@jancborchardt.net> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Encryption; + +use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; +use OC\Encryption\Exceptions\EncryptionHeaderToLargeException; +use OC\Encryption\Exceptions\ModuleDoesNotExistsException; +use OC\Files\Filesystem; +use OC\Files\View; +use OCP\Encryption\IEncryptionModule; +use OCP\Files\Storage; +use OCP\IConfig; + +class Util { + + const HEADER_START = 'HBEGIN'; + const HEADER_END = 'HEND'; + const HEADER_PADDING_CHAR = '-'; + + const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module'; + + /** + * block size will always be 8192 for a PHP stream + * @see https://bugs.php.net/bug.php?id=21641 + * @var integer + */ + protected $headerSize = 8192; + + /** + * block size will always be 8192 for a PHP stream + * @see https://bugs.php.net/bug.php?id=21641 + * @var integer + */ + protected $blockSize = 8192; + + /** @var View */ + protected $rootView; + + /** @var array */ + protected $ocHeaderKeys; + + /** @var \OC\User\Manager */ + protected $userManager; + + /** @var IConfig */ + protected $config; + + /** @var array paths excluded from encryption */ + protected $excludedPaths; + + /** @var \OC\Group\Manager $manager */ + protected $groupManager; + + /** + * + * @param View $rootView + * @param \OC\User\Manager $userManager + * @param \OC\Group\Manager $groupManager + * @param IConfig $config + */ + public function __construct( + View $rootView, + \OC\User\Manager $userManager, + \OC\Group\Manager $groupManager, + IConfig $config) { + + $this->ocHeaderKeys = [ + self::HEADER_ENCRYPTION_MODULE_KEY + ]; + + $this->rootView = $rootView; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->config = $config; + + $this->excludedPaths[] = 'files_encryption'; + } + + /** + * read encryption module ID from header + * + * @param array $header + * @return string + * @throws ModuleDoesNotExistsException + */ + public function getEncryptionModuleId(array $header = null) { + $id = ''; + $encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY; + + 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('Default encryption module missing'); + } + } + + return $id; + } + + /** + * create header for encrypted file + * + * @param array $headerData + * @param IEncryptionModule $encryptionModule + * @return string + * @throws EncryptionHeaderToLargeException if header has to many arguments + * @throws EncryptionHeaderKeyExistsException if header key is already in use + */ + public function createHeader(array $headerData, IEncryptionModule $encryptionModule) { + $header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':'; + foreach ($headerData as $key => $value) { + if (in_array($key, $this->ocHeaderKeys)) { + throw new EncryptionHeaderKeyExistsException($key); + } + $header .= $key . ':' . $value . ':'; + } + $header .= self::HEADER_END; + + if (strlen($header) > $this->getHeaderSize()) { + throw new EncryptionHeaderToLargeException(); + } + + $paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT); + + return $paddedHeader; + } + + /** + * go recursively through a dir and collect all files and sub files. + * + * @param string $dir relative to the users files folder + * @return array with list of files relative to the users files folder + */ + public function getAllFiles($dir) { + $result = array(); + $dirList = array($dir); + + while ($dirList) { + $dir = array_pop($dirList); + $content = $this->rootView->getDirectoryContent($dir); + + foreach ($content as $c) { + if ($c->getType() === 'dir') { + $dirList[] = $c->getPath(); + } else { + $result[] = $c->getPath(); + } + } + + } + + return $result; + } + + /** + * check if it is a file uploaded by the user stored in data/user/files + * or a metadata file + * + * @param string $path relative to the data/ folder + * @return boolean + */ + public function isFile($path) { + $parts = explode('/', Filesystem::normalizePath($path), 4); + if (isset($parts[2]) && $parts[2] === 'files') { + return true; + } + return false; + } + + /** + * return size of encryption header + * + * @return integer + */ + public function getHeaderSize() { + return $this->headerSize; + } + + /** + * return size of block read by a PHP stream + * + * @return integer + */ + public function getBlockSize() { + return $this->blockSize; + } + + /** + * get the owner and the path for the file relative to the owners files folder + * + * @param string $path + * @return array + * @throws \BadMethodCallException + */ + public function getUidAndFilename($path) { + + $parts = explode('/', $path); + $uid = ''; + if (count($parts) > 2) { + $uid = $parts[1]; + } + if (!$this->userManager->userExists($uid)) { + throw new \BadMethodCallException( + 'path needs to be relative to the system wide data folder and point to a user specific file' + ); + } + + $ownerPath = implode('/', array_slice($parts, 2)); + + return array($uid, Filesystem::normalizePath($ownerPath)); + + } + + /** + * Remove .path extension from a file path + * @param string $path Path that may identify a .part file + * @return string File path without .part extension + * @note this is needed for reusing keys + */ + public function stripPartialFileExtension($path) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + + if ( $extension === 'part') { + + $newLength = strlen($path) - 5; // 5 = strlen(".part") + $fPath = substr($path, 0, $newLength); + + // if path also contains a transaction id, we remove it too + $extension = pathinfo($fPath, PATHINFO_EXTENSION); + if(substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId") + $newLength = strlen($fPath) - strlen($extension) -1; + $fPath = substr($fPath, 0, $newLength); + } + return $fPath; + + } else { + return $path; + } + } + + public function getUserWithAccessToMountPoint($users, $groups) { + $result = array(); + if (in_array('all', $users)) { + $result = \OCP\User::getUsers(); + } else { + $result = array_merge($result, $users); + foreach ($groups as $group) { + $result = array_merge($result, \OC_Group::usersInGroup($group)); + } + } + + return $result; + } + + /** + * check if the file is stored on a system wide mount point + * @param string $path relative to /data/user with leading '/' + * @param string $uid + * @return boolean + */ + public function isSystemWideMountPoint($path, $uid) { + if (\OCP\App::isEnabled("files_external")) { + $mounts = \OC_Mount_Config::getSystemMountPoints(); + foreach ($mounts as $mount) { + if (strpos($path, '/files/' . $mount['mountpoint']) === 0) { + if ($this->isMountPointApplicableToUser($mount, $uid)) { + return true; + } + } + } + } + return false; + } + + /** + * check if mount point is applicable to user + * + * @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups'] + * @param string $uid + * @return boolean + */ + private function isMountPointApplicableToUser($mount, $uid) { + $acceptedUids = array('all', $uid); + // check if mount point is applicable for the user + $intersection = array_intersect($acceptedUids, $mount['applicable']['users']); + if (!empty($intersection)) { + return true; + } + // check if mount point is applicable for group where the user is a member + foreach ($mount['applicable']['groups'] as $gid) { + if ($this->groupManager->isInGroup($uid, $gid)) { + return true; + } + } + return false; + } + + /** + * check if it is a path which is excluded by ownCloud from encryption + * + * @param string $path + * @return boolean + */ + public function isExcluded($path) { + $normalizedPath = Filesystem::normalizePath($path); + $root = explode('/', $normalizedPath, 4); + if (count($root) > 1) { + + // detect alternative key storage root + $rootDir = $this->getKeyStorageRoot(); + if ($rootDir !== '' && + 0 === strpos( + Filesystem::normalizePath($path), + Filesystem::normalizePath($rootDir) + ) + ) { + return true; + } + + + //detect system wide folders + if (in_array($root[1], $this->excludedPaths)) { + return true; + } + + // detect user specific folders + if ($this->userManager->userExists($root[1]) + && in_array($root[2], $this->excludedPaths)) { + + return true; + } + } + return false; + } + + /** + * check if recovery key is enabled for user + * + * @param string $uid + * @return boolean + */ + public function recoveryEnabled($uid) { + $enabled = $this->config->getUserValue($uid, 'encryption', 'recovery_enabled', '0'); + + return ($enabled === '1') ? true : false; + } + + /** + * set new key storage root + * + * @param string $root new key store root relative to the data folder + */ + public function setKeyStorageRoot($root) { + $this->config->setAppValue('core', 'encryption_key_storage_root', $root); + } + + /** + * get key storage root + * + * @return string key storage root + */ + public function getKeyStorageRoot() { + return $this->config->getAppValue('core', 'encryption_key_storage_root', ''); + } + +} |