diff options
42 files changed, 3014 insertions, 125 deletions
diff --git a/.gitignore b/.gitignore index de467ec2cf9..3395774d232 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ /apps*/* !/apps/files !/apps/files_encryption +!/apps/encryption +!/apps/encryption_dummy !/apps/files_external !/apps/files_sharing !/apps/files_trashbin diff --git a/apps/encryption_dummy/appinfo/app.php b/apps/encryption_dummy/appinfo/app.php new file mode 100644 index 00000000000..fa17e676eda --- /dev/null +++ b/apps/encryption_dummy/appinfo/app.php @@ -0,0 +1,6 @@ +<?php + +$manager = \OC::$server->getEncryptionManager(); +$module = new \OCA\Encryption_Dummy\DummyModule(); +$manager->registerEncryptionModule($module); + diff --git a/apps/encryption_dummy/appinfo/info.xml b/apps/encryption_dummy/appinfo/info.xml new file mode 100644 index 00000000000..f62f6fb5dd6 --- /dev/null +++ b/apps/encryption_dummy/appinfo/info.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<info> + <id>encryption_dummy</id> + <name>dummy encryption module</name> + <description> + This module does nothing, it is used for testing purpose only + </description> + <licence>AGPL</licence> + <author>Bjoern Schiessle</author> + <requiremin>8</requiremin> + <shipped>true</shipped> + <rememberlogin>false</rememberlogin> + <types> + <filesystem/> + </types> + <ocsid>166047</ocsid> + <dependencies> + <lib>openssl</lib> + </dependencies> +</info> diff --git a/apps/encryption_dummy/appinfo/version b/apps/encryption_dummy/appinfo/version new file mode 100644 index 00000000000..8acdd82b765 --- /dev/null +++ b/apps/encryption_dummy/appinfo/version @@ -0,0 +1 @@ +0.0.1 diff --git a/apps/encryption_dummy/lib/dummymodule.php b/apps/encryption_dummy/lib/dummymodule.php new file mode 100644 index 00000000000..8ca9cd4f9af --- /dev/null +++ b/apps/encryption_dummy/lib/dummymodule.php @@ -0,0 +1,145 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCA\Encryption_Dummy; + +class DummyModule implements \OCP\Encryption\IEncryptionModule { + + /** @var boolean */ + protected $isWriteOperation; + + /** + * @return string defining the technical unique id + */ + public function getId() { + return "34876934"; + } + + /** + * In comparison to getKey() this function returns a human readable (maybe translated) name + * + * @return string + */ + public function getDisplayName() { + return "Dummy Encryption Module"; + } + + /** + * start receiving chunks from a file. This is the place where you can + * perform some initial step before starting encrypting/decrypting the + * chunks + * + * @param string $path to the file + * @param string $user who read/write the file (null for public access) + * @param array $header contains the header data read from the file + * @param array $accessList who has access to the file contains the key 'users' and 'public' + * + * $return array $header contain data as key-value pairs which should be + * written to the header, in case of a write operation + * or if no additional data is needed return a empty array + */ + public function begin($path, $user, $header, $accessList) { + return array(); + } + + /** + * last chunk received. This is the place where you can perform some final + * operation and return some remaining data if something is left in your + * buffer. + * + * @param string $path to the file + * @return string remained data which should be written to the file in case + * of a write operation + */ + public function end($path) { + + if ($this->isWriteOperation) { + $storage = \OC::$server->getEncryptionKeyStorage($this->getId()); + $storage->setFileKey($path, 'fileKey', 'foo'); + } + return ''; + } + + /** + * encrypt data + * + * @param string $data you want to encrypt + * @return mixed encrypted data + */ + public function encrypt($data) { + $this->isWriteOperation = true; + return $data; + } + + /** + * decrypt data + * + * @param string $data you want to decrypt + * @param string $user decrypt as user (null for public access) + * @return mixed decrypted data + */ + public function decrypt($data) { + $this->isWriteOperation=false; + return $data; + } + + /** + * update encrypted file, e.g. give additional users access to the file + * + * @param string $path path to the file which should be updated + * @param array $accessList who has access to the file contains the key 'users' and 'public' + * @return boolean + */ + public function update($path, $accessList) { + return true; + } + + /** + * should the file be encrypted or not + * + * @param string $path + * @return boolean + */ + public function shouldEncrypt($path) { + if (strpos($path, '/'. \OCP\User::getUser() . '/files/') === 0) { + return true; + } + + return false; + } + + /** + * calculate unencrypted size + * + * @param string $path to file + * @return integer unencrypted size + */ + public function calculateUnencryptedSize($path) { + return 42; + } + + public function getUnencryptedBlockSize() { + return 6126; + } + +}
\ No newline at end of file diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index 903715d4766..efbad26c9ef 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -27,6 +27,10 @@ * */ +// no php execution timeout for webdav +set_time_limit(0); + + // Backends $authBackend = new \OC\Connector\Sabre\Auth(); diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php index 06feb630f27..2a9f0359c91 100755 --- a/apps/files_encryption/tests/share.php +++ b/apps/files_encryption/tests/share.php @@ -62,9 +62,6 @@ class Share extends TestCase { \OC::registerShareHooks(); \OCA\Files_Sharing\Helper::registerHooks(); - // clear and register hooks - \OC_FileProxy::register(new \OCA\Files\Share\Proxy()); - // create users self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER1, true); self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER2, true); diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 85002ff982b..a222973fc34 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -51,8 +51,6 @@ OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); OCP\Util::addScript('files_sharing', 'share'); OCP\Util::addScript('files_sharing', 'external'); -OC_FileProxy::register(new OCA\Files\Share\Proxy()); - \OC::$server->getActivityManager()->registerExtension(function() { return new \OCA\Files_Sharing\Activity( \OC::$server->query('L10NFactory'), diff --git a/apps/files_sharing/lib/controllers/sharecontroller.php b/apps/files_sharing/lib/controllers/sharecontroller.php index c1a31d45aff..182342c22e5 100644 --- a/apps/files_sharing/lib/controllers/sharecontroller.php +++ b/apps/files_sharing/lib/controllers/sharecontroller.php @@ -27,7 +27,6 @@ namespace OCA\Files_Sharing\Controllers; -use Hoa\Core\Data\Data; use OC; use OC\Files\Filesystem; use OC_Files; diff --git a/apps/files_sharing/lib/helper.php b/apps/files_sharing/lib/helper.php index 712b9fa29dd..236be15a95a 100644 --- a/apps/files_sharing/lib/helper.php +++ b/apps/files_sharing/lib/helper.php @@ -36,6 +36,7 @@ class Helper { \OCP\Util::connectHook('OC_Filesystem', 'post_delete', '\OC\Files\Cache\Shared_Updater', 'postDeleteHook'); \OCP\Util::connectHook('OC_Filesystem', 'delete', '\OC\Files\Cache\Shared_Updater', 'deleteHook'); \OCP\Util::connectHook('OC_Filesystem', 'post_rename', '\OC\Files\Cache\Shared_Updater', 'renameHook'); + \OCP\Util::connectHook('OC_Filesystem', 'post_delete', '\OCA\Files_Sharing\Hooks', 'unshareChildren'); \OCP\Util::connectHook('OC_Appconfig', 'post_set_value', '\OCA\Files\Share\Maintainer', 'configChangeHook'); \OCP\Util::connectHook('OCP\Share', 'post_shared', '\OC\Files\Cache\Shared_Updater', 'postShareHook'); diff --git a/apps/files_sharing/lib/hooks.php b/apps/files_sharing/lib/hooks.php index dc7317c5c7d..c3588ecdfe5 100644 --- a/apps/files_sharing/lib/hooks.php +++ b/apps/files_sharing/lib/hooks.php @@ -22,6 +22,8 @@ namespace OCA\Files_Sharing; +use OC\Files\Filesystem; + class Hooks { public static function deleteUser($params) { @@ -35,4 +37,18 @@ class Hooks { $manager->removeUserShares($params['uid']); } + public static function unshareChildren($params) { + $path = Filesystem::getView()->getAbsolutePath($params['path']); + $view = new \OC\Files\View('/'); + + // find share mount points within $path and unmount them + $mountManager = \OC\Files\Filesystem::getMountManager(); + $mountedShares = $mountManager->findIn($path); + foreach ($mountedShares as $mount) { + if ($mount->getStorage()->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) { + $mountPoint = $mount->getMountPoint(); + $view->unlink($mountPoint); + } + } + } } diff --git a/apps/files_sharing/lib/proxy.php b/apps/files_sharing/lib/proxy.php deleted file mode 100644 index 538d8bcfecf..00000000000 --- a/apps/files_sharing/lib/proxy.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OCA\Files\Share; -use OCA\Files_Sharing\Helper; - -class Proxy extends \OC_FileProxy { - - /** - * check if the deleted folder contains share mount points and unshare them - * - * @param string $path - */ - public function preUnlink($path) { - $this->unshareChildren($path); - } - - /** - * check if the deleted folder contains share mount points and unshare them - * - * @param string $path - */ - public function preRmdir($path) { - $this->unshareChildren($path); - } - - /** - * unshare shared items below the deleted folder - * - * @param string $path - */ - private function unshareChildren($path) { - $view = new \OC\Files\View('/'); - - // find share mount points within $path and unmount them - $mountManager = \OC\Files\Filesystem::getMountManager(); - $mountedShares = $mountManager->findIn($path); - foreach ($mountedShares as $mount) { - if ($mount->getStorage()->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) { - $mountPoint = $mount->getMountPoint(); - $view->unlink($mountPoint); - } - } - } - -} diff --git a/apps/files_sharing/tests/proxy.php b/apps/files_sharing/tests/unsharechildren.php index cfdc7741733..b49180fdc83 100644 --- a/apps/files_sharing/tests/proxy.php +++ b/apps/files_sharing/tests/unsharechildren.php @@ -22,13 +22,13 @@ * */ +namespace OCA\Files_sharing\Tests; use OCA\Files\Share; -/** - * Class Test_Files_Sharing_Proxy - */ -class Test_Files_Sharing_Proxy extends OCA\Files_sharing\Tests\TestCase { +class UnshareChildren extends TestCase { + + protected $subsubfolder; const TEST_FOLDER_NAME = '/folder_share_api_test'; @@ -37,9 +37,7 @@ class Test_Files_Sharing_Proxy extends OCA\Files_sharing\Tests\TestCase { protected function setUp() { parent::setUp(); - // load proxies - OC::$CLASSPATH['OCA\Files\Share\Proxy'] = 'files_sharing/lib/proxy.php'; - OC_FileProxy::register(new OCA\Files\Share\Proxy()); + \OCP\Util::connectHook('OC_Filesystem', 'post_delete', '\OCA\Files_Sharing\Hooks', 'unshareChildren'); $this->folder = self::TEST_FOLDER_NAME; $this->subfolder = '/subfolder_share_api_test'; @@ -66,7 +64,7 @@ class Test_Files_Sharing_Proxy extends OCA\Files_sharing\Tests\TestCase { /** * @medium */ - function testpreUnlink() { + function testUnshareChildren() { $fileInfo2 = \OC\Files\Filesystem::getFileInfo($this->folder); diff --git a/apps/files_sharing/tests/updater.php b/apps/files_sharing/tests/updater.php index f019fc6a4b2..df1bbe1cc66 100644 --- a/apps/files_sharing/tests/updater.php +++ b/apps/files_sharing/tests/updater.php @@ -66,7 +66,6 @@ class Test_Files_Sharing_Updater extends OCA\Files_sharing\Tests\TestCase { \OC_App::enable('files_trashbin'); \OCA\Files_Trashbin\Trashbin::registerHooks(); - OC_FileProxy::register(new OCA\Files\Share\Proxy()); $fileinfo = \OC\Files\Filesystem::getFileInfo($this->folder); $this->assertTrue($fileinfo instanceof \OC\Files\FileInfo); diff --git a/l10n/l10n.pl b/l10n/l10n.pl index 4f8d8debb8d..0ff25944d6c 100644 --- a/l10n/l10n.pl +++ b/l10n/l10n.pl @@ -145,7 +145,7 @@ elsif( $task eq 'write' ){ my @js_strings = (); my $plurals; - foreach my $string ( @{$array} ){ + TRANSLATIONS: foreach my $string ( @{$array} ){ if( $string->msgid() eq '""' ){ # Translator information $plurals = getPluralInfo( $string->msgstr()); @@ -160,6 +160,7 @@ elsif( $task eq 'write' ){ my $identifier = "_" . $msgid."_::_".$msgid_plural . "_"; foreach my $variant ( sort { $a <=> $b} keys( %{$string->msgstr_n()} )){ + next TRANSLATIONS if $string->msgstr_n()->{$variant} eq '""'; push( @variants, $string->msgstr_n()->{$variant} ); } @@ -168,7 +169,7 @@ elsif( $task eq 'write' ){ } else{ # singular translations - next if $string->msgstr() eq '""'; + next TRANSLATIONS if $string->msgstr() eq '""'; push( @strings, $string->msgid()." => ".$string->msgstr()); push( @js_strings, $string->msgid()." : ".$string->msgstr()); } diff --git a/lib/base.php b/lib/base.php index 66dd4f20f04..b441d5f1927 100644 --- a/lib/base.php +++ b/lib/base.php @@ -178,7 +178,7 @@ class OC { // search the 3rdparty folder OC::$THIRDPARTYROOT = OC_Config::getValue('3rdpartyroot', null); OC::$THIRDPARTYWEBROOT = OC_Config::getValue('3rdpartyurl', null); - + if (empty(OC::$THIRDPARTYROOT) && empty(OC::$THIRDPARTYWEBROOT)) { if (file_exists(OC::$SERVERROOT . '/3rdparty')) { OC::$THIRDPARTYROOT = OC::$SERVERROOT; @@ -193,7 +193,7 @@ class OC { . ' folder in the ownCloud folder or the folder above.' . ' You can also configure the location in the config.php file.'); } - + // search the apps folder $config_paths = OC_Config::getValue('apps_paths', array()); if (!empty($config_paths)) { @@ -642,6 +642,8 @@ class OC { self::registerShareHooks(); self::registerLogRotate(); self::registerLocalAddressBook(); + self::registerEncryptionWrapper(); + self::registerEncryptionHooks(); //make sure temporary files are cleaned up $tmpManager = \OC::$server->getTempManager(); @@ -698,6 +700,45 @@ class OC { }); } + private static function registerEncryptionWrapper() { + $enabled = self::$server->getEncryptionManager()->isEnabled(); + if ($enabled) { + \OC\Files\Filesystem::addStorageWrapper('oc_encryption', function ($mountPoint, $storage) { + $parameters = array('storage' => $storage, 'mountPoint' => $mountPoint); + $manager = \OC::$server->getEncryptionManager(); + $util = new \OC\Encryption\Util(new \OC\Files\View(), \OC::$server->getUserManager()); + $user = \OC::$server->getUserSession()->getUser(); + $logger = \OC::$server->getLogger(); + $uid = $user ? $user->getUID() : null; + return new \OC\Files\Storage\Wrapper\Encryption($parameters, $manager,$util, $logger, $uid); + }); + } + + } + + private static function registerEncryptionHooks() { + $enabled = self::$server->getEncryptionManager()->isEnabled(); + if ($enabled) { + $user = \OC::$server->getUserSession()->getUser(); + $uid = ''; + if ($user) { + $uid = $user->getUID(); + } + $updater = new \OC\Encryption\Update( + new \OC\Files\View(), + new \OC\Encryption\Util(new \OC\Files\View(), \OC::$server->getUserManager()), + \OC\Files\Filesystem::getMountManager(), + \OC::$server->getEncryptionManager(), + $uid + ); + \OCP\Util::connectHook('OCP\Share', 'post_shared', $updater, 'postShared'); + \OCP\Util::connectHook('OCP\Share', 'post_unshare', $updater, 'postUnshared'); + + //\OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Files_Encryption\Hooks', 'postUnmount'); + //\OCP\Util::connectHook('OC_Filesystem', 'umount', 'OCA\Files_Encryption\Hooks', 'preUnmount'); + } + } + /** * register hooks for the cache */ diff --git a/lib/private/encryption/exceptions/encryptionheaderkeyexistsexception.php b/lib/private/encryption/exceptions/encryptionheaderkeyexistsexception.php new file mode 100644 index 00000000000..d401f0323ba --- /dev/null +++ b/lib/private/encryption/exceptions/encryptionheaderkeyexistsexception.php @@ -0,0 +1,29 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Exceptions; + + +class EncryptionHeaderKeyExistsException extends \Exception { + +}
\ No newline at end of file diff --git a/lib/private/encryption/exceptions/modulealreadyexistsexception.php b/lib/private/encryption/exceptions/modulealreadyexistsexception.php new file mode 100644 index 00000000000..41fc0188e24 --- /dev/null +++ b/lib/private/encryption/exceptions/modulealreadyexistsexception.php @@ -0,0 +1,28 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Exceptions; + +class ModuleAlreadyExistsException extends \Exception { + +} diff --git a/lib/private/encryption/exceptions/moduledoesnotexistsexception.php b/lib/private/encryption/exceptions/moduledoesnotexistsexception.php new file mode 100644 index 00000000000..5507bd03dab --- /dev/null +++ b/lib/private/encryption/exceptions/moduledoesnotexistsexception.php @@ -0,0 +1,28 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Exceptions; + +class ModuleDoesNotExistsException extends \Exception { + +} diff --git a/lib/private/encryption/keys/factory.php b/lib/private/encryption/keys/factory.php new file mode 100644 index 00000000000..a214b238615 --- /dev/null +++ b/lib/private/encryption/keys/factory.php @@ -0,0 +1,52 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Keys; + +use OC\Encryption\Util; +use OC\Files\View; +use OC\User; + +/** + * Factory provides KeyStorage for different encryption modules + */ +class Factory { + /** @var array */ + protected $instances = array(); + + /** + * get a KeyStorage instance + * + * @param string $encryptionModuleId + * @param View $view + * @param Util $util + * @return Storage + */ + public function get($encryptionModuleId,View $view, Util $util) { + if (!isset($this->instances[$encryptionModuleId])) { + $this->instances[$encryptionModuleId] = new Storage($encryptionModuleId, $view, $util); + } + return $this->instances[$encryptionModuleId]; + } + +} diff --git a/lib/private/encryption/keys/storage.php b/lib/private/encryption/keys/storage.php new file mode 100644 index 00000000000..041db2a2cb8 --- /dev/null +++ b/lib/private/encryption/keys/storage.php @@ -0,0 +1,320 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Keys; + +use OC\Encryption\Util; +use OC\Files\View; +use OCA\Files_Encryption\Exception\EncryptionException; + +class Storage implements \OCP\Encryption\Keys\IStorage { + + /** @var View */ + private $view; + + /** @var Util */ + private $util; + + // base dir where all the file related keys are stored + private $keys_base_dir; + private $encryption_base_dir; + + private $keyCache = array(); + + /** @var string */ + private $encryptionModuleId; + + /** + * @param string $encryptionModuleId + * @param View $view + * @param Util $util + */ + public function __construct($encryptionModuleId, View $view, Util $util) { + $this->view = $view; + $this->util = $util; + $this->encryptionModuleId = $encryptionModuleId; + + $this->encryption_base_dir = '/files_encryption'; + $this->keys_base_dir = $this->encryption_base_dir .'/keys'; + } + + /** + * get user specific key + * + * @param string $uid ID if the user for whom we want the key + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getUserKey($uid, $keyId) { + $path = $this->constructUserKeyPath($keyId, $uid); + return $this->getKey($path); + } + + /** + * get file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getFileKey($path, $keyId) { + $keyDir = $this->getFileKeyDir($path); + return $this->getKey($keyDir . $keyId); + } + + /** + * get system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getSystemUserKey($keyId) { + $path = $this->constructUserKeyPath($keyId); + return $this->getKey($path); + } + + /** + * set user specific key + * + * @param string $uid ID if the user for whom we want the key + * @param string $keyId id of the key + * @param mixed $key + */ + public function setUserKey($uid, $keyId, $key) { + $path = $this->constructUserKeyPath($keyId, $uid); + return $this->setKey($path, $key); + } + + /** + * set file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * @param boolean + */ + public function setFileKey($path, $keyId, $key) { + $keyDir = $this->getFileKeyDir($path); + return $this->setKey($keyDir . $keyId, $key); + } + + /** + * set system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * @param mixed $key + * + * @return mixed key + */ + public function setSystemUserKey($keyId, $key) { + $path = $this->constructUserKeyPath($keyId); + return $this->setKey($path, $key); + } + + /** + * delete user specific key + * + * @param string $uid ID if the user for whom we want to delete the key + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteUserKey($uid, $keyId) { + $path = $this->constructUserKeyPath($keyId, $uid); + return $this->view->unlink($path); + } + + /** + * delete file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteFileKey($path, $keyId) { + $keyDir = $this->getFileKeyDir($path); + return $this->view->unlink($keyDir . $keyId); + } + + /** + * delete all file keys for a given file + * + * @param string $path to the file + * @return boolean + */ + public function deleteAllFileKeys($path) { + $keyDir = $this->getFileKeyDir($path); + return $this->view->deleteAll(dirname($keyDir)); + } + + /** + * delete system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteSystemUserKey($keyId) { + $path = $this->constructUserKeyPath($keyId); + return $this->view->unlink($path); + } + + + /** + * construct path to users key + * + * @param string $keyId + * @param string $uid + * @return string + */ + protected function constructUserKeyPath($keyId, $uid = null) { + + if ($uid === null) { + $path = $this->encryption_base_dir . '/' . $this->encryptionModuleId . '/' . $keyId; + } else { + $path = '/' . $uid . $this->encryption_base_dir . '/' + . $this->encryptionModuleId . '/' . $uid . '.' . $keyId; + } + + return $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 $path path to the file, relative to data/ + * @return string + * @throws EncryptionException + * @internal param string $keyId + */ + private function getFileKeyDir($path) { + + if ($this->view->is_dir($path)) { + throw new EncryptionException('file was expected but directory was given', EncryptionException::GENERIC); + } + + list($owner, $filename) = $this->util->getUidAndFilename($path); + $filename = $this->util->stripPartialFileExtension($filename); + + // in case of system wide mount points the keys are stored directly in the data directory + if ($this->util->isSystemWideMountPoint($filename)) { + $keyPath = $this->keys_base_dir . $filename . '/'; + } else { + $keyPath = '/' . $owner . $this->keys_base_dir . $filename . '/'; + } + + return \OC\Files\Filesystem::normalizePath($keyPath . $this->encryptionModuleId . '/', false); + } + + /** + * move keys if a file was renamed + * + * @param string $source + * @param string $target + * @param string $owner + * @param bool $systemWide + */ + public function renameKeys($source, $target, $owner, $systemWide) { + if ($systemWide) { + $sourcePath = $this->keys_base_dir . $source . '/'; + $targetPath = $this->keys_base_dir . $target . '/'; + } else { + $sourcePath = '/' . $owner . $this->keys_base_dir . $source . '/'; + $targetPath = '/' . $owner . $this->keys_base_dir . $target . '/'; + } + + if ($this->view->file_exists($sourcePath)) { + $this->keySetPreparation(dirname($targetPath)); + $this->view->rename($sourcePath, $targetPath); + } + } + + /** + * Make preparations to filesystem for saving a keyfile + * + * @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('/', $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..5164025239c --- /dev/null +++ b/lib/private/encryption/manager.php @@ -0,0 +1,170 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption; + +use OCP\Encryption\IEncryptionModule; + +class Manager implements \OCP\Encryption\IManager { + + /** @var array */ + protected $encryptionModules; + + /** @var \OCP\IConfig */ + protected $config; + + /** + * @param \OCP\IConfig $config + */ + public function __construct(\OCP\IConfig $config) { + $this->encryptionModules = array(); + $this->config = $config; + } + + /** + * 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'; + } + + /** + * Registers an encryption module + * + * @param IEncryptionModule $module + * @throws Exceptions\ModuleAlreadyExistsException + */ + public function registerEncryptionModule(IEncryptionModule $module) { + $id = $module->getId(); + $name = $module->getDisplayName(); + if (isset($this->encryptionModules[$id])) { + $message = 'Id "' . $id . '" already used by encryption module "' . $name . '"'; + throw new Exceptions\ModuleAlreadyExistsException($message); + + } + + $defaultEncryptionModuleId = $this->getDefaultEncryptionModuleId(); + + if (empty($defaultEncryptionModuleId)) { + $this->setDefaultEncryptionModule($id); + } + + $this->encryptionModules[$id] = $module; + } + + /** + * Unregisters an encryption module + * + * @param IEncryptionModule $module + */ + public function unregisterEncryptionModule(IEncryptionModule $module) { + unset($this->encryptionModules[$module->getId()]); + } + + /** + * get a list of all encryption modules + * + * @return IEncryptionModule[] + */ + public function getEncryptionModules() { + return $this->encryptionModules; + } + + /** + * get a specific encryption module + * + * @param string $moduleId + * @return IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function getEncryptionModule($moduleId) { + if (isset($this->encryptionModules[$moduleId])) { + return $this->encryptionModules[$moduleId]; + } else { + $message = "Module with id: $moduleId does not exists."; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + } + + /** + * get default encryption module + * + * @return \OCP\Encryption\IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function getDefaultEncryptionModule() { + $defaultModuleId = $this->getDefaultEncryptionModuleId(); + if (!empty($defaultModuleId)) { + if (isset($this->encryptionModules[$defaultModuleId])) { + return $this->encryptionModules[$defaultModuleId]; + } 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->config->setAppValue('core', 'default_encryption_module', $moduleId); + return true; + } catch (\Exception $e) { + return false; + } + + } + + /** + * get default encryption module Id + * + * @return string + */ + protected function getDefaultEncryptionModuleId() { + try { + return $this->config->getAppValue('core', 'default_encryption_module'); + } catch (\Exception $e) { + return ''; + } + } + + +} diff --git a/lib/private/encryption/update.php b/lib/private/encryption/update.php new file mode 100644 index 00000000000..649cf0285a6 --- /dev/null +++ b/lib/private/encryption/update.php @@ -0,0 +1,111 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption; + +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; + + /** + * + * @param \OC\Files\View $view + * @param \OC\Encryption\Util $util + * @param \OC\Files\Mount\Manager $mountManager + * @param \OC\Encryption\Manager $encryptionManager + * @param string $uid + */ + public function __construct( + View $view, + Util $util, + Mount\Manager $mountManager, + Manager $encryptionManager, + $uid + ) { + + $this->view = $view; + $this->util = $util; + $this->mountManager = $mountManager; + $this->encryptionManager = $encryptionManager; + $this->uid = $uid; + } + + public function postShared($params) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $this->update($params['fileSource']); + } + } + + public function postUnshared($params) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $this->update($params['fileSource']); + } + } + + /** + * update keyfiles and share keys recursively + * + * @param int $fileSource file source id + */ + private function update($fileSource) { + $path = \OC\Files\Filesystem::getPath($fileSource); + $absPath = '/' . $this->uid . '/files' . $path; + + $mount = $this->mountManager->find($path); + $mountPoint = $mount->getMountPoint(); + + // if a folder was shared, get a list of all (sub-)folders + if ($this->view->is_dir($absPath)) { + $allFiles = $this->util->getAllFiles($absPath, $mountPoint); + } else { + $allFiles = array($absPath); + } + + $encryptionModule = $this->encryptionManager->getDefaultEncryptionModule(); + + foreach ($allFiles as $path) { + $usersSharing = $this->util->getSharingUsersArray($path); + $encryptionModule->update($absPath, $usersSharing); + } + } + +}
\ No newline at end of file diff --git a/lib/private/encryption/util.php b/lib/private/encryption/util.php new file mode 100644 index 00000000000..2c6ff266841 --- /dev/null +++ b/lib/private/encryption/util.php @@ -0,0 +1,401 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption; + +use OC\Encryption\Exceptions\EncryptionHeaderToLargeException; +use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; +use OCP\Encryption\IEncryptionModule; + +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 \OC\Files\View */ + protected $view; + + /** @var array */ + protected $ocHeaderKeys; + + /** @var \OC\User\Manager */ + protected $userManager; + + /** @var array paths excluded from encryption */ + protected $excludedPaths; + + /** + * @param \OC\Files\View $view root view + */ + public function __construct(\OC\Files\View $view, \OC\User\Manager $userManager) { + $this->ocHeaderKeys = [ + self::HEADER_ENCRYPTION_MODULE_KEY + ]; + + $this->view = $view; + $this->userManager = $userManager; + + $this->excludedPaths[] = 'files_encryption'; + } + + /** + * read encryption module ID from header + * + * @param array $header + * @return string + */ + public function getEncryptionModuleId(array $header) { + $id = ''; + $encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY; + + if (isset($header[$encryptionModuleKey])) { + $id = $header[$encryptionModuleKey]; + } + + return $id; + } + + /** + * 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 + * @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('header key "'. $key . '" already reserved by ownCloud'); + } + $header .= $key . ':' . $value . ':'; + } + $header .= self::HEADER_END; + + if (strlen($header) > $this->getHeaderSize()) { + throw new EncryptionHeaderToLargeException('max header size exceeded', EncryptionException::ENCRYPTION_HEADER_TO_LARGE); + } + + $paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT); + + return $paddedHeader; + } + + /** + * Find, sanitise and format users sharing a file + * @note This wraps other methods into a portable bundle + * @param string $path path relative to current users files folder + * @return array + */ + public function getSharingUsersArray($path) { + + // Make sure that a share key is generated for the owner too + list($owner, $ownerPath) = $this->getUidAndFilename($path); + + // always add owner to the list of users with access to the file + $userIds = array($owner); + + if (!$this->isFile($ownerPath)) { + return array('users' => $userIds, 'public' => false); + } + + $ownerPath = substr($ownerPath, strlen('/files')); + $ownerPath = $this->stripPartialFileExtension($ownerPath); + + // Find out who, if anyone, is sharing the file + $result = \OCP\Share::getUsersSharingFile($ownerPath, $owner); + $userIds = \array_merge($userIds, $result['users']); + $public = $result['public'] || $result['remote']; + + // 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->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); + } + + /** + * go recursively through a dir and collect all files and sub files. + * + * @param string $dir relative to the users files folder + * @param strinf $mountPoint + * @return array with list of files relative to the users files folder + */ + public function getAllFiles($dir, $mountPoint = '') { + $result = array(); + $dirList = array($dir); + + while ($dirList) { + $dir = array_pop($dirList); + $content = $this->view->getDirectoryContent($dir); + + foreach ($content as $c) { + // getDirectoryContent() returns the paths relative to the mount points, so we need + // to re-construct the complete path + $path = ($mountPoint !== '') ? $mountPoint . '/' . $c['path'] : $c['path']; + if ($c['type'] === 'dir') { + $dirList[] = $path; + } else { + $result[] = $path; + } + } + + } + + return $result; + } + + /** + * check if it is a file uploaded by the user stored in data/user/files + * or a metadata file + * + * @param string $path + * @return boolean + */ + protected function isFile($path) { + if (substr($path, 0, strlen('/files/')) === '/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 owner + * + * @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'); + } + + $pathinfo = pathinfo($path); + $partfile = false; + $parentFolder = false; + if (array_key_exists('extension', $pathinfo) && $pathinfo['extension'] === 'part') { + // if the real file exists we check this file + $filePath = $pathinfo['dirname'] . '/' . $pathinfo['filename']; + if ($this->view->file_exists($filePath)) { + $pathToCheck = $pathinfo['dirname'] . '/' . $pathinfo['filename']; + } else { // otherwise we look for the parent + $pathToCheck = $pathinfo['dirname']; + $parentFolder = true; + } + $partfile = true; + } else { + $pathToCheck = $path; + } + + $pathToCheck = substr($pathToCheck, strlen('/' . $uid)); + + $this->view->chroot('/' . $uid); + $owner = $this->view->getOwner($pathToCheck); + + // Check that UID is valid + if (!$this->userManager->userExists($owner)) { + throw new \BadMethodCallException('path needs to be relative to the system wide data folder and point to a user specific file'); + } + + \OC\Files\Filesystem::initMountPoints($owner); + + $info = $this->view->getFileInfo($pathToCheck); + $this->view->chroot('/' . $owner); + $ownerPath = $this->view->getPath($info->getId()); + $this->view->chroot('/'); + + if ($parentFolder) { + $ownerPath = $ownerPath . '/'. $pathinfo['filename']; + } + + if ($partfile) { + $ownerPath = $ownerPath . '.' . $pathinfo['extension']; + } + + return array( + $owner, + \OC\Files\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; + } + } + + protected 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 '/' + * @return boolean + */ + public function isSystemWideMountPoint($path) { + $normalizedPath = ltrim($path, '/'); + if (\OCP\App::isEnabled("files_external")) { + $mounts = \OC_Mount_Config::getSystemMountPoints(); + foreach ($mounts as $mount) { + if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) { + if ($this->isMountPointApplicableToUser($mount)) { + 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) { + $root = explode('/', $path, 2); + if (isset($root[0])) { + if (in_array($root[0], $this->excludedPaths)) { + return true; + } + } + return false; + } + +} diff --git a/lib/private/files/fileinfo.php b/lib/private/files/fileinfo.php index 82c8f3de690..03aad56e103 100644 --- a/lib/private/files/fileinfo.php +++ b/lib/private/files/fileinfo.php @@ -170,6 +170,13 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { /** * @return int */ + public function getUnencryptedSize() { + return isset($this->data['unencrypted_size']) ? $this->data['unencrypted_size'] : 0; + } + + /** + * @return int + */ public function getPermissions() { return $this->data['permissions']; } diff --git a/lib/private/files/storage/wrapper/encryption.php b/lib/private/files/storage/wrapper/encryption.php new file mode 100644 index 00000000000..44fc2124f7a --- /dev/null +++ b/lib/private/files/storage/wrapper/encryption.php @@ -0,0 +1,319 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Encryption\Exceptions\ModuleDoesNotExistsException; + +class Encryption extends Wrapper { + + /** @var string */ + private $mountPoint; + + /** @var \OC\Encryption\Util */ + private $util; + + /** @var \OC\Encryption\Manager */ + private $encryptionManager; + + /** @var \OC\Log */ + private $logger; + + /** @var string */ + private $uid; + + /** @var array */ + private $unencryptedSize; + + /** + * @param array $parameters + * @param \OC\Encryption\Manager $encryptionManager + * @param \OC\Encryption\Util $util + * @param \OC\Log $logger + * @param string $uid user who perform the read/write operation (null for public access) + */ + public function __construct( + $parameters, + \OC\Encryption\Manager $encryptionManager = null, + \OC\Encryption\Util $util = null, + \OC\Log $logger = null, + $uid = null + ) { + + $this->mountPoint = $parameters['mountPoint']; + $this->encryptionManager = $encryptionManager; + $this->util = $util; + $this->logger = $logger; + $this->uid = $uid; + $this->unencryptedSize = array(); + parent::__construct($parameters); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + $size = 0; + $fullPath = $this->getFullPath($path); + + $encryptedSize = $this->storage->filesize($path); + + $info = $this->getCache()->get($path); + + if($encryptedSize > 0 && $info['encrypted']) { + $size = $info['unencrypted_size']; + if ($size <= 0) { + $encryptionModule = $this->getEncryptionModule($path); + if ($encryptionModule) { + $size = $encryptionModule->calculateUnencryptedSize($fullPath); + $this->getCache()->update($info['fileid'], array('unencrypted_size' => $size)); + } + } + } else if (isset($this->unencryptedSize[$fullPath]) && isset($info['fileid'])) { + $size = $this->unencryptedSize[$fullPath]; + $info['encrypted'] = true; + $info['unencrypted_size'] = $size; + $info['size'] = $encryptedSize; + $this->getCache()->put($path, $info); + } + + return $size; + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + + $data = null; + $encryptionModule = $this->getEncryptionModule($path); + + if ($encryptionModule) { + + $handle = $this->fopen($path, 'r'); + + if (is_resource($handle)) { + while (!feof($handle)) { + $data .= fread($handle, $this->util->getBlockSize()); + } + } + } else { + $data = $this->storage->file_get_contents($path); + } + + return $data; + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + // file put content will always be translated to a stream write + $handle = $this->fopen($path, 'w'); + fwrite($handle, $data); + return fclose($handle); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + if ($this->util->isExcluded($path)) { + return $this->storage->unlink($path); + } + + $encryptionModule = $this->getEncryptionModule($path); + if ($encryptionModule) { + $keyStorage = \OC::$server->getEncryptionKeyStorage($encryptionModule->getId()); + $keyStorage->deleteAllFileKeys($this->getFullPath($path)); + } + + return $this->storage->unlink($path); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + if ($this->util->isExcluded($path1)) { + return $this->storage->rename($path1, $path2); + } + + $fullPath1 = $this->getFullPath($path1); + list($owner, $source) = $this->util->getUidAndFilename($fullPath1); + $result = $this->storage->rename($path1, $path2); + if ($result) { + $fullPath2 = $this->getFullPath($path2); + $systemWide = $this->util->isSystemWideMountPoint($this->mountPoint); + list(, $target) = $this->util->getUidAndFilename($fullPath2); + $encryptionModule = $this->getEncryptionModule($path2); + if ($encryptionModule) { + $keyStorage = \OC::$server->getEncryptionKeyStorage($encryptionModule->getId()); + $keyStorage->renameKeys($source, $target, $owner, $systemWide); + } + } + + return $result; + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + // todo copy encryption keys, get users with access to the file and reencrypt + // or is this to encryption module specific? Then we can hand this over + return $this->storage->copy($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + + $shouldEncrypt = false; + $encryptionModule = null; + $header = $this->getHeader($path); + $fullPath = $this->getFullPath($path); + $encryptionModuleId = $this->util->getEncryptionModuleId($header); + + $size = $unencryptedSize = 0; + if ($this->file_exists($path)) { + $size = $this->storage->filesize($path); + $unencryptedSize = $this->filesize($path); + } + + try { + + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + ) { + if (!empty($encryptionModuleId)) { + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + } else { + $encryptionModule = $this->encryptionManager->getDefaultEncryptionModule(); + } + $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath); + } else { + // only get encryption module if we found one in the header + if (!empty($encryptionModuleId)) { + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + $shouldEncrypt = true; + } + } + } catch (ModuleDoesNotExistsException $e) { + $this->logger->warning('Encryption module "' . $encryptionModuleId . + '" not found, file will be stored unencrypted'); + } + + if($shouldEncrypt === true && !$this->util->isExcluded($path) && $encryptionModule !== null) { + $source = $this->storage->fopen($path, $mode); + $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header, + $this->uid, $encryptionModule, $this->storage, $this, $this->util, $mode, + $size, $unencryptedSize); + return $handle; + } else { + return $this->storage->fopen($path, $mode); + } + } + + /** + * return full path, including mount point + * + * @param string $path relative to mount point + * @return string full path including mount point + */ + protected function getFullPath($path) { + return \OC\Files\Filesystem::normalizePath($this->mountPoint . '/' . $path); + } + + /** + * read header from file + * + * @param string $path + * @return array + */ + protected function getHeader($path) { + $header = ''; + if ($this->storage->file_exists($path)) { + $handle = $this->storage->fopen($path, 'r'); + $header = fread($handle, $this->util->getHeaderSize()); + fclose($handle); + } + return $this->util->readHeader($header); + } + + /** + * read encryption module needed to read/write the file located at $path + * + * @param string $path + * @return \OCP\Encryption\IEncryptionModule|null + */ + protected function getEncryptionModule($path) { + $encryptionModule = null; + $header = $this->getHeader($path); + $encryptionModuleId = $this->util->getEncryptionModuleId($header); + if (!empty($encryptionModuleId)) { + try { + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + } catch (ModuleDoesNotExistsException $e) { + $this->logger->critical('Encryption module defined in "' . $path . '" mot loaded!'); + throw $e; + } + } + return $encryptionModule; + } + + public function updateUnencryptedSize($path, $unencryptedSize) { + $this->unencryptedSize[$path] = $unencryptedSize; + } + +} diff --git a/lib/private/files/stream/encryption.php b/lib/private/files/stream/encryption.php new file mode 100644 index 00000000000..ddef9067bad --- /dev/null +++ b/lib/private/files/stream/encryption.php @@ -0,0 +1,385 @@ +<?php + +/** + * ownCloud - Encryption stream wrapper + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Files\Stream; + +use Icewind\Streams\Wrapper; +use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; + +class Encryption extends Wrapper { + + /** @var \OC\Encryption\Util */ + protected $util; + + /** @var \OCP\Encryption\IEncryptionModule */ + protected $encryptionModule; + + /** @var \OC\Files\Storage\Storage */ + protected $storage; + + /** @var \OC\Files\Storage\Wrapper\Encryption */ + protected $encryptionStorage; + + /** @var string */ + protected $internalPath; + + /** @var integer */ + protected $size; + + /** @var integer */ + protected $position; + + /** @var integer */ + protected $unencryptedSize; + + /** @var integer */ + protected $unencryptedBlockSize; + + /** @var array */ + protected $header; + + /** @var string */ + protected $fullPath; + + /** + * header data returned by the encryption module, will be written to the file + * in case of a write operation + * + * @var array + */ + protected $newHeader; + + /** + * user who perform the read/write operation null for public access + * + * @var string + */ + protected $uid; + + /** @var bool */ + protected $readOnly; + + /** @var array */ + protected $expectedContextProperties; + + public function __construct() { + $this->expectedContextProperties = array( + 'source', + 'storage', + 'internalPath', + 'fullPath', + 'encryptionModule', + 'header', + 'uid', + 'util', + 'size', + 'unencryptedSize', + 'encryptionStorage' + ); + } + + + /** + * Wraps a stream with the provided callbacks + * + * @param resource $source + * @param string $internalPath relative to mount point + * @param string $fullPath relative to data/ + * @param array $header + * @param sting $uid + * @param \OCP\Encryption\IEncryptionModule $encryptionModule + * @param \OC\Files\Storage\Storage $storage + * @param OC\Files\Storage\Wrapper\Encryption $encStorage + * @param \OC\Encryption\Util $util + * @param string $mode + * @param int $size + * @param int $unencryptedSize + * @return resource + * + * @throws \BadMethodCallException + */ + public static function wrap($source, $internalPath, $fullPath, array $header, + $uid, \OCP\Encryption\IEncryptionModule $encryptionModule, + \OC\Files\Storage\Storage $storage, \OC\Files\Storage\Wrapper\Encryption $encStorage, + \OC\Encryption\Util $util, $mode, $size, $unencryptedSize) { + + $context = stream_context_create(array( + 'ocencryption' => array( + 'source' => $source, + 'storage' => $storage, + 'internalPath' => $internalPath, + 'fullPath' => $fullPath, + 'encryptionModule' => $encryptionModule, + 'header' => $header, + 'uid' => $uid, + 'util' => $util, + 'size' => $size, + 'unencryptedSize' => $unencryptedSize, + 'encryptionStorage' => $encStorage + ) + )); + + return self::wrapSource($source, $mode, $context, 'ocencryption', 'OC\Files\Stream\Encryption'); + } + + /** + * add stream wrapper + * + * @param resource $source + * @param string $mode + * @param array $context + * @param string $protocol + * @param string $class + * @return resource + * @throws \BadMethodCallException + */ + protected static function wrapSource($source, $mode, $context, $protocol, $class) { + try { + stream_wrapper_register($protocol, $class); + if (@rewinddir($source) === false) { + $wrapped = fopen($protocol . '://', $mode, false, $context); + } else { + $wrapped = opendir($protocol . '://', $context); + } + } catch (\BadMethodCallException $e) { + stream_wrapper_unregister($protocol); + throw $e; + } + stream_wrapper_unregister($protocol); + return $wrapped; + } + + /** + * Load the source from the stream context and return the context options + * + * @param string $name + * @return array + * @throws \BadMethodCallException + */ + protected function loadContext($name) { + $context = parent::loadContext($name); + + foreach ($this->expectedContextProperties as $property) { + if (isset($context[$property])) { + $this->{$property} = $context[$property]; + } else { + throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set'); + } + } + return $context; + + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $this->loadContext('ocencryption'); + + $this->position = 0; + $this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize(); + + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + ) { + // We're writing a new file so start write counter with 0 bytes + // TODO can we remove this completely? + //$this->unencryptedSize = 0; + //$this->size = 0; + $this->readOnly = false; + } else { + $this->readOnly = true; + } + + $sharePath = $this->fullPath; + if (!$this->storage->file_exists($this->internalPath)) { + $sharePath = dirname($path); + } + + $accessList = $this->util->getSharingUsersArray($sharePath); + $this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $this->header, $accessList); + + return true; + + } + + public function stream_read($count) { + + $result = ''; + + // skip the header if we read the file from the beginning + if ($this->position === 0 && !empty($this->header)) { + parent::stream_read($this->util->getBlockSize()); + } + + while ($count > 0) { + $remainingLength = $count; + // update the cache of the current block + $data = parent::stream_read($this->util->getBlockSize()); + $decrypted = $this->encryptionModule->decrypt($data); + // determine the relative position in the current block + $blockPosition = ($this->position % $this->unencryptedBlockSize); + // if entire read inside current block then only position needs to be updated + if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { + $result .= substr($decrypted, $blockPosition, $remainingLength); + $this->position += $remainingLength; + $count = 0; + // otherwise remainder of current block is fetched, the block is flushed and the position updated + } else { + $result .= substr($decrypted, $blockPosition); + $this->position += ($this->unencryptedBlockSize - $blockPosition); + $count -= ($this->unencryptedBlockSize - $blockPosition); + } + } + return $result; + + } + + public function stream_write($data) { + + if ($this->position === 0) { + $this->writeHeader(); + } + + $length = 0; + // loop over $data to fit it in 6126 sized unencrypted blocks + while (strlen($data) > 0) { + $remainingLength = strlen($data); + + // read current block + $currentBlock = parent::stream_read($this->util->getBlockSize()); + $decrypted = $this->encryptionModule->decrypt($currentBlock, $this->uid); + + // for seekable streams the pointer is moved back to the beginning of the encrypted block + // flush will start writing there when the position moves to another block + $positionInFile = floor($this->position / $this->unencryptedBlockSize) * + $this->util->getBlockSize() + $this->util->getHeaderSize(); + $resultFseek = parent::stream_seek($positionInFile); + + // only allow writes on seekable streams, or at the end of the encrypted stream + if ($resultFseek || $positionInFile === $this->size) { + + // determine the relative position in the current block + $blockPosition = ($this->position % $this->unencryptedBlockSize); + // check if $data fits in current block + // if so, overwrite existing data (if any) + // update position and liberate $data + if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { + $decrypted = substr($decrypted, 0, $blockPosition) + . $data . substr($decrypted, $blockPosition + $remainingLength); + $encrypted = $this->encryptionModule->encrypt($decrypted); + parent::stream_write($encrypted); + $this->position += $remainingLength; + $length += $remainingLength; + $data = ''; + // if $data doens't fit the current block, the fill the current block and reiterate + // after the block is filled, it is flushed and $data is updatedxxx + } else { + $decrypted = substr($decrypted, 0, $blockPosition) . + substr($data, 0, $this->unencryptedBlockSize - $blockPosition); + $encrypted = $this->encryptionModule->encrypt($decrypted); + parent::stream_write($encrypted); + $this->position += ($this->unencryptedBlockSize - $blockPosition); + $this->size = max($this->size, $this->stream_tell()); + $length += ($this->unencryptedBlockSize - $blockPosition); + $data = substr($data, $this->unencryptedBlockSize - $blockPosition); + } + } else { + $encrypted = $this->encryptionModule->encrypt($data); + parent::stream_write($encrypted); + $data = ''; + } + } + $this->unencryptedSize = max($this->unencryptedSize, $this->position); + return $length; + } + + public function stream_tell() { + return $this->position; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + + $return = false; + + switch ($whence) { + case SEEK_SET: + if ($offset < $this->unencryptedSize && $offset >= 0) { + $newPosition = $offset; + } + break; + case SEEK_CUR: + if ($offset >= 0) { + $newPosition = $offset + $this->position; + } + break; + case SEEK_END: + if ($this->unencryptedSize + $offset >= 0) { + $newPosition = $this->unencryptedSize + $offset; + } + break; + default: + return $return; + } + + $newFilePosition = floor($newPosition / $this->unencryptedBlockSize) + * $this->util->getBlockSize() + $this->util->getHeaderSize(); + + if (parent::stream_seek($newFilePosition)) { + $this->position = $newPosition; + $return = true; + } + return $return; + + } + + public function stream_close() { + $this->flush(); + return parent::stream_close(); + } + + /** + * tell encryption module that we are done and write remaining data to the file + */ + protected function flush() { + $remainingData = $this->encryptionModule->end($this->fullPath); + if ($this->readOnly === false) { + if(!empty($remainingData)) { + parent::stream_write($remainingData); + } + $this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize); + } + } + + + /** + * write header at beginning of encrypted file + * + * @throws EncryptionHeaderKeyExistsException if header key is already in use + */ + private function writeHeader() { + $header = $this->util->createHeader($this->newHeader, $this->encryptionModule); + parent::stream_write($header); + } + +} diff --git a/lib/private/l10n.php b/lib/private/l10n.php index b16a0468e23..95b80bebdb2 100644 --- a/lib/private/l10n.php +++ b/lib/private/l10n.php @@ -305,7 +305,7 @@ class OC_L10N implements \OCP\IL10N { $this->init(); $identifier = "_${text_singular}_::_${text_plural}_"; if( array_key_exists($identifier, $this->translations)) { - return new OC_L10N_String($this, $identifier, $parameters, $count, array($text_singular, $text_plural)); + return new OC_L10N_String( $this, $identifier, $parameters, $count ); }else{ if($count === 1) { return new OC_L10N_String($this, $text_singular, $parameters, $count); diff --git a/lib/private/l10n/string.php b/lib/private/l10n/string.php index 21fe765618b..6167a6737dc 100644 --- a/lib/private/l10n/string.php +++ b/lib/private/l10n/string.php @@ -41,11 +41,6 @@ class OC_L10N_String{ protected $parameters; /** - * @var array - */ - protected $plurals; - - /** * @var integer */ protected $count; @@ -53,12 +48,11 @@ class OC_L10N_String{ /** * @param OC_L10N $l10n */ - public function __construct($l10n, $text, $parameters, $count = 1, $plurals = array()) { + public function __construct($l10n, $text, $parameters, $count = 1) { $this->l10n = $l10n; $this->text = $text; $this->parameters = $parameters; $this->count = $count; - $this->plurals = $plurals; } public function __toString() { @@ -69,19 +63,7 @@ class OC_L10N_String{ if(is_array($translations[$this->text])) { $fn = $this->l10n->getPluralFormFunction(); $id = $fn($this->count); - - if ($translations[$this->text][$id] !== '') { - // The translation of this plural case is not empty, so use it - $text = $translations[$this->text][$id]; - } else { - // We didn't find the plural in the language, - // so we fall back to english. - $id = ($id != 0) ? 1 : 0; - if (isset($this->plurals[$id])) { - // Fallback to the english plural - $text = $this->plurals[$id]; - } - } + $text = $translations[$this->text][$id]; } else{ $text = $translations[$this->text]; diff --git a/lib/private/server.php b/lib/private/server.php index 5daf0d55e32..592f8d9a042 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -78,9 +78,18 @@ class Server extends SimpleContainer implements IServerContainer { $this->registerService('ContactsManager', function ($c) { return new ContactsManager(); }); + $this->registerService('PreviewManager', function (Server $c) { return new PreviewManager($c->getConfig()); }); + + $this->registerService('EncryptionManager', function (Server $c) { + return new Encryption\Manager($c->getConfig()); + }); + + $this->registerService('EncryptionKeyStorageFactory', function ($c) { + return new Encryption\Keys\Factory(); + }); $this->registerService('TagMapper', function(Server $c) { return new TagMapper($c->getDatabaseConnection()); }); @@ -390,6 +399,24 @@ class Server extends SimpleContainer implements IServerContainer { } /** + * @return \OC\Encryption\Manager + */ + function getEncryptionManager() { + return $this->query('EncryptionManager'); + } + + /** + * @param string $encryptionModuleId encryption module ID + * + * @return \OCP\Encryption\Keys\IStorage + */ + function getEncryptionKeyStorage($encryptionModuleId) { + $view = new \OC\Files\View(); + $util = new \OC\Encryption\Util($view, \OC::$server->getUserManager()); + return $this->query('EncryptionKeyStorageFactory')->get($encryptionModuleId, $view, $util); + } + + /** * The current request object holding all information about the request * currently being processed is returned from this method. * In case the current execution was not initiated by a web request null is returned diff --git a/lib/public/encryption/iencryptionmodule.php b/lib/public/encryption/iencryptionmodule.php new file mode 100644 index 00000000000..2527e35e639 --- /dev/null +++ b/lib/public/encryption/iencryptionmodule.php @@ -0,0 +1,115 @@ +<?php + +/** + * ownCloud - public interface of ownCloud for encryption modules + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCP\Encryption; + +interface IEncryptionModule { + + /** + * @return string defining the technical unique id + */ + public function getId(); + + /** + * In comparison to getKey() this function returns a human readable (maybe translated) name + * + * @return string + */ + public function getDisplayName(); + + /** + * start receiving chunks from a file. This is the place where you can + * perform some initial step before starting encrypting/decrypting the + * chunks + * + * @param string $path to the file + * @param string $user who read/write the file (null for public access) + * @param array $header contains the header data read from the file + * @param array $accessList who has access to the file contains the key 'users' and 'public' + * + * $return array $header contain data as key-value pairs which should be + * written to the header, in case of a write operation + * or if no additional data is needed return a empty array + */ + public function begin($path, $user, $header, $accessList); + + /** + * last chunk received. This is the place where you can perform some final + * operation and return some remaining data if something is left in your + * buffer. + * + * @param string $path to the file + * @return string remained data which should be written to the file in case + * of a write operation + */ + public function end($path); + + /** + * encrypt data + * + * @param string $data you want to encrypt + * @return mixed encrypted data + */ + public function encrypt($data); + + /** + * decrypt data + * + * @param string $data you want to decrypt + * @return mixed decrypted data + */ + public function decrypt($data); + + /** + * update encrypted file, e.g. give additional users access to the file + * + * @param string $path path to the file which should be updated + * @param array $accessList who has access to the file contains the key 'users' and 'public' + * @return boolean + */ + public function update($path, $accessList); + + /** + * should the file be encrypted or not + * + * @param string $path + * @return boolean + */ + public function shouldEncrypt($path); + + /** + * calculate unencrypted size + * + * @param string $path to file + * @return integer unencrypted size + */ + public function calculateUnencryptedSize($path); + + /** + * get size of the unencrypted payload per block. + * ownCloud read/write files with a block size of 8192 byte + * + * @return integer + */ + public function getUnencryptedBlockSize(); +} diff --git a/lib/public/encryption/imanager.php b/lib/public/encryption/imanager.php new file mode 100644 index 00000000000..9a12e401593 --- /dev/null +++ b/lib/public/encryption/imanager.php @@ -0,0 +1,92 @@ +<?php + +/** + * ownCloud - manage encryption modules + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCP\Encryption; +// +// TODO: move exceptions to OCP +// +use OC\Encryption\Exceptions\ModuleDoesNotExistsException; +use OC\Encryption\Exceptions\ModuleAlreadyExistsException; + +/** + * This class provides access to files encryption apps. + * + */ +interface IManager { + + /** + * Check if encryption is available (at least one encryption module needs to be enabled) + * + * @return bool true if enabled, false if not + */ + function isEnabled(); + + /** + * Registers an encryption module + * + * @param IEncryptionModule $module + * @throws ModuleAlreadyExistsException + */ + function registerEncryptionModule(IEncryptionModule $module); + + /** + * Unregisters an encryption module + * + * @param IEncryptionModule $module + */ + function unregisterEncryptionModule(IEncryptionModule $module); + + /** + * get a list of all encryption modules + * + * @return array + */ + function getEncryptionModules(); + + + /** + * get a specific encryption module + * + * @param string $moduleId + * @return IEncryptionModule + * @throws ModuleDoesNotExistsException + */ + function getEncryptionModule($moduleId); + + /** + * get default encryption module + * + * @return \OCP\Encryption\IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function getDefaultEncryptionModule(); + + /** + * set default encryption module Id + * + * @param string $moduleId + * @return string + */ + public function setDefaultEncryptionModule($moduleId); + +} diff --git a/lib/public/encryption/keys/istorage.php b/lib/public/encryption/keys/istorage.php new file mode 100644 index 00000000000..24f6efd6e51 --- /dev/null +++ b/lib/public/encryption/keys/istorage.php @@ -0,0 +1,117 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCP\Encryption\Keys; + +interface IStorage { + + /** + * get user specific key + * + * @param string $uid ID if the user for whom we want the key + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getUserKey($uid, $keyId); + + /** + * get file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getFileKey($path, $keyId); + + /** + * get system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getSystemUserKey($keyId); + + /** + * set user specific key + * + * @param string $uid ID if the user for whom we want the key + * @param string $keyId id of the key + * @param mixed $key + */ + public function setUserKey($uid, $keyId, $key); + + /** + * set file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * @param boolean + */ + public function setFileKey($path, $keyId, $key); + + /** + * set system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * @param mixed $key + * + * @return mixed key + */ + public function setSystemUserKey($keyId, $key); + + /** + * delete user specific key + * + * @param string $uid ID if the user for whom we want to delete the key + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteUserKey($uid, $keyId); + + /** + * delete file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteFileKey($path, $keyId); + + /** + * delete system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteSystemUserKey($keyId); + +} diff --git a/settings/admin.php b/settings/admin.php index 9f5f36b2210..4416af1fe37 100644 --- a/settings/admin.php +++ b/settings/admin.php @@ -81,6 +81,23 @@ $template->assign('shareExcludeGroups', $excludeGroups); $excludedGroupsList = $appConfig->getValue('core', 'shareapi_exclude_groups_list', ''); $excludedGroupsList = explode(',', $excludedGroupsList); // FIXME: this should be JSON! $template->assign('shareExcludedGroupsList', implode('|', $excludedGroupsList)); +$template->assign('encryptionEnabled', \OC::$server->getEncryptionManager()->isEnabled()); +$encryptionModules = \OC::$server->getEncryptionManager()->getEncryptionModules(); +try { + $defaultEncryptionModule = \OC::$server->getEncryptionManager()->getDefaultEncryptionModule(); + $defaultEncryptionModuleId = $defaultEncryptionModule->getId(); +} catch (Exception $e) { + $defaultEncryptionModule = null; +} +$encModulues = array(); +foreach ($encryptionModules as $module) { + $encModulues[$module->getId()]['displayName'] = $module->getDisplayName(); + $encModulues[$module->getId()]['default'] = false; + if ($defaultEncryptionModule && $module->getId() === $defaultEncryptionModuleId) { + $encModulues[$module->getId()]['default'] = true; + } +} +$template->assign('encryptionModules', $encModulues); // If the current web root is non-empty but the web root from the config is, // and system cron is used, the URL generator fails to build valid URLs. @@ -142,6 +159,7 @@ $formsAndMore = array_merge($formsAndMore, $formsMap); // add bottom hardcoded forms from the template $formsAndMore[] = array('anchor' => 'backgroundjobs', 'section-name' => $l->t('Cron')); $formsAndMore[] = array('anchor' => 'shareAPI', 'section-name' => $l->t('Sharing')); +$formsAndMore[] = array('anchor' => 'encryptionAPI', 'section-name' => $l->t('Server Side Encryption')); $formsAndMore[] = array('anchor' => 'mail_general_settings', 'section-name' => $l->t('Email Server')); $formsAndMore[] = array('anchor' => 'log-section', 'section-name' => $l->t('Log')); $formsAndMore[] = array('anchor' => 'admin-tips', 'section-name' => $l->t('Tips & tricks')); diff --git a/settings/css/settings.css b/settings/css/settings.css index 050914beef6..869a113762b 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -407,3 +407,12 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { display: inline-block; padding: 3px 0; } + +#selectEncryptionModules { + margin-left: 30px; + padding: 10px; +} + +#encryptionModules { + padding: 10px; +}
\ No newline at end of file diff --git a/settings/js/admin.js b/settings/js/admin.js index 6ec1a061fc9..d1dc23382f9 100644 --- a/settings/js/admin.js +++ b/settings/js/admin.js @@ -54,6 +54,22 @@ $(document).ready(function(){ $('#shareAPI p:not(#enable)').toggleClass('hidden', !this.checked); }); + $('#encryptionEnabled').change(function() { + $('#encryptionAPI div#selectEncryptionModules').toggleClass('hidden'); + }); + + $('#encryptionAPI input').change(function() { + var value = $(this).val(); + if ($(this).attr('type') === 'checkbox') { + if (this.checked) { + value = 'yes'; + } else { + value = 'no'; + } + } + OC.AppConfig.setValue('core', $(this).attr('name'), value); + }); + $('#shareAPI input:not(#excludedGroups)').change(function() { var value = $(this).val(); if ($(this).attr('type') === 'checkbox') { diff --git a/settings/templates/admin.php b/settings/templates/admin.php index 42ae42a4d02..44680674e8e 100644 --- a/settings/templates/admin.php +++ b/settings/templates/admin.php @@ -291,6 +291,30 @@ if ($_['cronErrors']) { </p> </div> +<div class="section" id='encryptionAPI'> + <h2><?php p($l->t('Server Side Encryption'));?></h2> + <p id="enable"> + <input type="checkbox" name="encryption_enabled" id="encryptionEnabled" + value="1" <?php if ($_['encryptionEnabled']) print_unescaped('checked="checked"'); ?> /> + <label for="encryptionEnabled"><?php p($l->t('Enable Server-Side-Encryption'));?></label><br/> + </p> + <div id='selectEncryptionModules' class="<?php if (!$_['encryptionEnabled']) { p('hidden'); }?>"> + <?php if (empty($_['encryptionModules'])): p('No encryption module loaded, please load a encryption module in the app menu'); + else: ?> + <h3>Select default encryption module:</h3> + <fieldset id='encryptionModules'> + <?php foreach ($_['encryptionModules'] as $id => $module): ?> + <input type="radio" id="<?php p($id) ?>" + name="default_encryption_module" + value="<?php p($id) ?>" + <?php if($module['default']) { p('checked'); } ?>> + <label for="<?php p($id) ?>"><?php p($module['displayName']) ?></label><br /> + <?php endforeach;?> + </fieldset> + <?php endif; ?> + </div> +</div> + <div class="section"> <form id="mail_general_settings" class="mail_settings"> <h2><?php p($l->t('Email Server'));?></h2> diff --git a/tests/data/l10n/ru.json b/tests/data/l10n/ru.json index 7041205e761..177b14a6b20 100644 --- a/tests/data/l10n/ru.json +++ b/tests/data/l10n/ru.json @@ -1,7 +1,6 @@ { "translations" : { - "_%n file_::_%n files_" : ["%n файл", "%n файла", "%n файлов"], - "_%n missing plural_::_%n missing plurals_" : ["", "", ""] + "_%n file_::_%n files_" : ["%n файл", "%n файла", "%n файлов"] }, "pluralForm" : "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" } diff --git a/tests/lib/encryption/keys/storage.php b/tests/lib/encryption/keys/storage.php new file mode 100644 index 00000000000..c2e5bdbd3d1 --- /dev/null +++ b/tests/lib/encryption/keys/storage.php @@ -0,0 +1,280 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace Test\Encryption\Keys; + +use OC\Encryption\Keys\Storage; +use Test\TestCase; + +class StorageTest extends TestCase { + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $util; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $view; + + public function setUp() { + parent::setUp(); + + $this->util = $this->getMockBuilder('OC\Encryption\Util') + ->disableOriginalConstructor() + ->getMock(); + + $this->view = $this->getMockBuilder('OC\Files\View') + ->disableOriginalConstructor() + ->getMock(); + + } + + public function testSetFileKey() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(false); + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setFileKey('user1/files/foo.txt', 'fileKey', 'key') + ); + } + + public function testGetFileKey() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(false); + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getFileKey('user1/files/foo.txt', 'fileKey') + ); + } + + public function testSetFileKeySystemWide() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(true); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setFileKey('user1/files/foo.txt', 'fileKey', 'key') + ); + } + + public function testGetFileKeySystemWide() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(true); + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getFileKey('user1/files/foo.txt', 'fileKey') + ); + } + + public function testSetSystemUserKey() { + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/files_encryption/encModule/shareKey_56884'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setSystemUserKey('shareKey_56884', 'key') + ); + } + + public function testSetUserKey() { + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setUserKey('user1', 'publicKey', 'key') + ); + } + + public function testGetSystemUserKey() { + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/files_encryption/encModule/shareKey_56884')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/files_encryption/encModule/shareKey_56884')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getSystemUserKey('shareKey_56884') + ); + } + + public function testGetUserKey() { + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getUserKey('user1', 'publicKey') + ); + } + + public function testDeleteUserKey() { + $this->view->expects($this->once()) + ->method('unlink') + ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->deleteUserKey('user1', 'publicKey') + ); + } + + public function testDeleteSystemUserKey() { + $this->view->expects($this->once()) + ->method('unlink') + ->with($this->equalTo('/files_encryption/encModule/shareKey_56884')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->deleteSystemUserKey('shareKey_56884') + ); + } + + public function testDeleteFileKeySystemWide() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(true); + $this->view->expects($this->once()) + ->method('unlink') + ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->deleteFileKey('user1/files/foo.txt', 'fileKey') + ); + } + + public function testDeleteFileKey() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(false); + $this->view->expects($this->once()) + ->method('unlink') + ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->deleteFileKey('user1/files/foo.txt', 'fileKey') + ); + } + +} diff --git a/tests/lib/encryption/managertest.php b/tests/lib/encryption/managertest.php new file mode 100644 index 00000000000..ab297bae0cb --- /dev/null +++ b/tests/lib/encryption/managertest.php @@ -0,0 +1,114 @@ +<?php + +namespace Test\Encryption; + +use OC\Encryption\Manager; +use Test\TestCase; + +class ManagerTest extends TestCase { + + public function testManagerIsDisabled() { + $config = $this->getMock('\OCP\IConfig'); + $m = new Manager($config); + $this->assertFalse($m->isEnabled()); + } + + public function testManagerIsDisabledIfEnabledButNoModules() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getAppValue')->willReturn(true); + $m = new Manager($config); + $this->assertFalse($m->isEnabled()); + } + + public function testManagerIsDisabledIfDisabledButModules() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getAppValue')->willReturn(false); + $em = $this->getMock('\OCP\Encryption\IEncryptionModule'); + $em->expects($this->any())->method('getId')->willReturn(0); + $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0'); + $m = new Manager($config); + $m->registerEncryptionModule($em); + $this->assertFalse($m->isEnabled()); + } + + public function testManagerIsEnabled() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getSystemValue')->willReturn(true); + $config->expects($this->any())->method('getAppValue')->willReturn('yes'); + $m = new Manager($config); + $this->assertTrue($m->isEnabled()); + } + + /** + * @expectedException \OC\Encryption\Exceptions\ModuleAlreadyExistsException + * @expectedExceptionMessage Id "0" already used by encryption module "TestDummyModule0" + */ + public function testModuleRegistration() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getAppValue')->willReturn('yes'); + $em = $this->getMock('\OCP\Encryption\IEncryptionModule'); + $em->expects($this->any())->method('getId')->willReturn(0); + $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0'); + $m = new Manager($config); + $m->registerEncryptionModule($em); + $this->assertSame(1, count($m->getEncryptionModules())); + $m->registerEncryptionModule($em); + } + + public function testModuleUnRegistration() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getAppValue')->willReturn(true); + $em = $this->getMock('\OCP\Encryption\IEncryptionModule'); + $em->expects($this->any())->method('getId')->willReturn(0); + $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0'); + $m = new Manager($config); + $m->registerEncryptionModule($em); + $this->assertSame(1, + count($m->getEncryptionModules()) + ); + $m->unregisterEncryptionModule($em); + $this->assertEmpty($m->getEncryptionModules()); + } + + /** + * @expectedException \OC\Encryption\Exceptions\ModuleDoesNotExistsException + * @expectedExceptionMessage Module with id: unknown does not exists. + */ + public function testGetEncryptionModuleUnknown() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getAppValue')->willReturn(true); + $em = $this->getMock('\OCP\Encryption\IEncryptionModule'); + $em->expects($this->any())->method('getId')->willReturn(0); + $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0'); + $m = new Manager($config); + $m->registerEncryptionModule($em); + $this->assertSame(1, count($m->getEncryptionModules())); + $m->getEncryptionModule('unknown'); + } + + public function testGetEncryptionModule() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getAppValue')->willReturn(true); + $em = $this->getMock('\OCP\Encryption\IEncryptionModule'); + $em->expects($this->any())->method('getId')->willReturn(0); + $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0'); + $m = new Manager($config); + $m->registerEncryptionModule($em); + $this->assertSame(1, count($m->getEncryptionModules())); + $en0 = $m->getEncryptionModule(0); + $this->assertEquals(0, $en0->getId()); + } + + public function testGetDefaultEncryptionModule() { + $config = $this->getMock('\OCP\IConfig'); + $config->expects($this->any())->method('getAppValue')->willReturn(true); + $em = $this->getMock('\OCP\Encryption\IEncryptionModule'); + $em->expects($this->any())->method('getId')->willReturn(0); + $em->expects($this->any())->method('getDisplayName')->willReturn('TestDummyModule0'); + $m = new Manager($config); + $m->registerEncryptionModule($em); + $this->assertSame(1, count($m->getEncryptionModules())); + $en0 = $m->getEncryptionModule(0); + $this->assertEquals(0, $en0->getId()); + } +} diff --git a/tests/lib/encryption/utiltest.php b/tests/lib/encryption/utiltest.php new file mode 100644 index 00000000000..00a9ab9c578 --- /dev/null +++ b/tests/lib/encryption/utiltest.php @@ -0,0 +1,101 @@ +<?php + +namespace Test\Encryption; + +use OC\Encryption\Util; +use Test\TestCase; + +class UtilTest extends TestCase { + + /** + * 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 \PHPUnit_Framework_MockObject_MockObject */ + protected $view; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $userManager; + + public function setUp() { + parent::setUp(); + $this->view = $this->getMockBuilder('OC\Files\View') + ->disableOriginalConstructor() + ->getMock(); + + $this->userManager = $this->getMockBuilder('OC\User\Manager') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @dataProvider providesHeadersForEncryptionModule + */ + public function testGetEncryptionModuleId($expected, $header) { + $u = new Util($this->view, $this->userManager); + $id = $u->getEncryptionModuleId($header); + $this->assertEquals($expected, $id); + } + + public function providesHeadersForEncryptionModule() { + return [ + ['', []], + ['', ['1']], + [2, ['oc_encryption_module' => 2]], + ]; + } + + /** + * @dataProvider providesHeaders + */ + public function testReadHeader($header, $expected, $moduleId) { + $expected['oc_encryption_module'] = $moduleId; + $u = new Util($this->view, $this->userManager); + $result = $u->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'); + $em->expects($this->any())->method('getId')->willReturn($moduleId); + + $u = new Util($this->view, $this->userManager); + $result = $u->createHeader($header, $em); + $this->assertEquals($expected, $result); + } + + public function providesHeaders() { + return [ + [str_pad('HBEGIN:oc_encryption_module:0:HEND', $this->headerSize, '-', STR_PAD_RIGHT) + , [], '0'], + [str_pad('HBEGIN:oc_encryption_module:0:custom_header:foo:HEND', $this->headerSize, '-', STR_PAD_RIGHT) + , ['custom_header' => 'foo'], '0'], + ]; + } + + /** + * @expectedException \OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException + */ + public function testCreateHeaderFailed() { + + $header = array('header1' => 1, 'header2' => 2, 'oc_encryption_module' => 'foo'); + + $em = $this->getMock('\OCP\Encryption\IEncryptionModule'); + $em->expects($this->any())->method('getId')->willReturn('moduleId'); + + $u = new Util($this->view, $this->userManager); + $u->createHeader($header, $em); + } + +} diff --git a/tests/lib/l10n.php b/tests/lib/l10n.php index 0307dd459e5..d5f9a5ca3fa 100644 --- a/tests/lib/l10n.php +++ b/tests/lib/l10n.php @@ -42,24 +42,6 @@ class Test_L10n extends \Test\TestCase { */ } - public function russianMissingPluralTranslationsData() { - return array( - array(1, '1 missing plural'), - array(2, '2 missing plurals'), - array(6, '6 missing plurals'), - ); - } - - /** - * @dataProvider russianMissingPluralTranslationsData - */ - public function testRussianMissingPluralTranslations($count, $expected) { - $l = new OC_L10N('test'); - $l->load(OC::$SERVERROOT.'/tests/data/l10n/ru.json'); - - $this->assertEquals($expected, (string)$l->n('%n missing plural', '%n missing plurals', $count)); - } - public function testCzechPluralTranslations() { $l = new OC_L10N('test'); $transFile = OC::$SERVERROOT.'/tests/data/l10n/cs.json'; |