diff options
58 files changed, 902 insertions, 130 deletions
diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php new file mode 100644 index 00000000000..480baab6baf --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php @@ -0,0 +1,50 @@ +<?php +/** + * @copyright Copyright (c) 2016, ownCloud, Inc. + * + * @author Joas Schilling <coding@schilljs.com> + * @author Robin Appelman <robin@icewind.nl> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @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\DAV\Tests\unit\Connector\Sabre\RequestTest; + +use OC\Files\View; +use Test\Traits\EncryptionTrait; + +/** + * Class EncryptionMasterKeyUploadTest + * + * @group DB + * + * @package OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest + */ +class EncryptionMasterKeyUploadTest extends UploadTest { + use EncryptionTrait; + + protected function setupUser($name, $password) { + $this->createUser($name, $password); + $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); + // we use the master key + \OC::$server->getConfig()->setAppValue('encryption', 'useMasterKey', '1'); + $this->setupForUser($name, $password); + $this->loginWithEncryption($name); + return new View('/' . $name . '/files'); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php index e65d58b816f..c0cba121386 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php @@ -41,6 +41,8 @@ class EncryptionUploadTest extends UploadTest { $this->createUser($name, $password); $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); + // we use per-user keys + \OC::$server->getConfig()->setAppValue('encryption', 'useMasterKey', '0'); $this->setupForUser($name, $password); $this->loginWithEncryption($name); return new View('/' . $name . '/files'); diff --git a/apps/encryption/appinfo/app.php b/apps/encryption/appinfo/app.php index 950166dca2b..4f54f0e7251 100644 --- a/apps/encryption/appinfo/app.php +++ b/apps/encryption/appinfo/app.php @@ -31,4 +31,5 @@ $app = new Application([], $encryptionSystemReady); if ($encryptionSystemReady) { $app->registerEncryptionModule(); $app->registerHooks(); + $app->setUp(); } diff --git a/apps/encryption/appinfo/info.xml b/apps/encryption/appinfo/info.xml index 7cfdc934386..f35a87aa4f2 100644 --- a/apps/encryption/appinfo/info.xml +++ b/apps/encryption/appinfo/info.xml @@ -19,7 +19,7 @@ <user>user-encryption</user> <admin>admin-encryption</admin> </documentation> - <version>1.7.1</version> + <version>2.0.0</version> <types> <filesystem/> </types> @@ -33,6 +33,13 @@ </settings> <commands> <command>OCA\Encryption\Command\EnableMasterKey</command> + <command>OCA\Encryption\Command\DisableMasterKey</command> <command>OCA\Encryption\Command\MigrateKeys</command> </commands> + + <repair-steps> + <post-migration> + <step>OCA\Encryption\Migration\SetMasterKeyStatus</step> + </post-migration> + </repair-steps> </info> diff --git a/apps/encryption/lib/AppInfo/Application.php b/apps/encryption/lib/AppInfo/Application.php index 56c2dafdabd..dd9d173c8eb 100644 --- a/apps/encryption/lib/AppInfo/Application.php +++ b/apps/encryption/lib/AppInfo/Application.php @@ -67,7 +67,11 @@ class Application extends \OCP\AppFramework\App { $session = $this->getContainer()->query('Session'); $session->setStatus(Session::RUN_MIGRATION); } - if ($this->encryptionManager->isEnabled() && $encryptionSystemReady) { + + } + + public function setUp() { + if ($this->encryptionManager->isEnabled()) { /** @var Setup $setup */ $setup = $this->getContainer()->query('UserSetup'); $setup->setupSystem(); @@ -77,7 +81,6 @@ class Application extends \OCP\AppFramework\App { /** * register hooks */ - public function registerHooks() { if (!$this->config->getSystemValue('maintenance', false)) { @@ -193,7 +196,8 @@ class Application extends \OCP\AppFramework\App { $c->getAppName(), $server->getRequest(), $server->getL10N($c->getAppName()), - $c->query('Session') + $c->query('Session'), + $server->getEncryptionManager() ); }); diff --git a/apps/encryption/lib/Command/DisableMasterKey.php b/apps/encryption/lib/Command/DisableMasterKey.php new file mode 100644 index 00000000000..97c2ad40b61 --- /dev/null +++ b/apps/encryption/lib/Command/DisableMasterKey.php @@ -0,0 +1,89 @@ +<?php +/** + * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\Encryption\Command; + + +use OCA\Encryption\Util; +use OCP\IConfig; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class DisableMasterKey extends Command { + + /** @var Util */ + protected $util; + + /** @var IConfig */ + protected $config; + + /** @var QuestionHelper */ + protected $questionHelper; + + /** + * @param Util $util + * @param IConfig $config + * @param QuestionHelper $questionHelper + */ + public function __construct(Util $util, + IConfig $config, + QuestionHelper $questionHelper) { + + $this->util = $util; + $this->config = $config; + $this->questionHelper = $questionHelper; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('encryption:disable-master-key') + ->setDescription('Disable the master key and use per-user keys instead. Only available for fresh installations with no existing encrypted data! There is no way to enable it again.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) { + + $isMasterKeyEnabled = $this->util->isMasterKeyEnabled(); + + if(!$isMasterKeyEnabled) { + $output->writeln('Master key already disabled'); + } else { + $question = new ConfirmationQuestion( + 'Warning: Only perform this operation for a fresh installations with no existing encrypted data! ' + . 'There is no way to enable the master key again. ' + . 'We strongly recommend to keep the master key, it provides significant performance improvements ' + . 'and is easier to handle for both, users and administrators. ' + . 'Do you really want to switch to per-user keys? (y/n) ', false); + if ($this->questionHelper->ask($input, $output, $question)) { + $this->config->setAppValue('encryption', 'useMasterKey', '0'); + $output->writeln('Master key successfully disabled.'); + } else { + $output->writeln('aborted.'); + } + } + + } + +} diff --git a/apps/encryption/lib/Controller/StatusController.php b/apps/encryption/lib/Controller/StatusController.php index 0776a84ceb4..9ec9fd1234b 100644 --- a/apps/encryption/lib/Controller/StatusController.php +++ b/apps/encryption/lib/Controller/StatusController.php @@ -28,6 +28,7 @@ namespace OCA\Encryption\Controller; use OCA\Encryption\Session; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; +use OCP\Encryption\IManager; use OCP\IL10N; use OCP\IRequest; @@ -39,20 +40,26 @@ class StatusController extends Controller { /** @var Session */ private $session; + /** @var IManager */ + private $encryptionManager; + /** * @param string $AppName * @param IRequest $request * @param IL10N $l10n * @param Session $session + * @param IManager $encryptionManager */ public function __construct($AppName, IRequest $request, IL10N $l10n, - Session $session + Session $session, + IManager $encryptionManager ) { parent::__construct($AppName, $request); $this->l = $l10n; $this->session = $session; + $this->encryptionManager = $encryptionManager; } /** @@ -78,9 +85,15 @@ class StatusController extends Controller { break; case Session::NOT_INITIALIZED: $status = 'interactionNeeded'; - $message = (string)$this->l->t( - 'Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again.' - ); + if ($this->encryptionManager->isEnabled()) { + $message = (string)$this->l->t( + 'Encryption App is enabled, but your keys are not initialized. Please log-out and log-in again.' + ); + } else { + $message = (string)$this->l->t( + 'Please enable server side encryption in the admin settings in order to use the encryption module.' + ); + } break; case Session::INIT_SUCCESSFUL: $status = 'success'; diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php index 7f7665a24fc..6869177ac31 100644 --- a/apps/encryption/lib/Crypto/Encryption.php +++ b/apps/encryption/lib/Crypto/Encryption.php @@ -569,4 +569,13 @@ class Encryption implements IEncryptionModule { public function isReadyForUser($user) { return $this->keyManager->userHasKeys($user); } + + /** + * We only need a detailed access list if the master key is not enabled + * + * @return bool + */ + public function needDetailedAccessList() { + return !$this->util->isMasterKeyEnabled(); + } } diff --git a/apps/encryption/lib/KeyManager.php b/apps/encryption/lib/KeyManager.php index 6b260c39bfb..6039aaaaa0e 100644 --- a/apps/encryption/lib/KeyManager.php +++ b/apps/encryption/lib/KeyManager.php @@ -179,8 +179,8 @@ class KeyManager { return; } - $masterKey = $this->getPublicMasterKey(); - if (empty($masterKey)) { + $publicMasterKey = $this->getPublicMasterKey(); + if (empty($publicMasterKey)) { $keyPair = $this->crypt->createKeyPair(); // Save public key @@ -193,6 +193,15 @@ class KeyManager { $header = $this->crypt->generateHeader(); $this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey); } + + if (!$this->session->isPrivateKeySet()) { + $masterKey = $this->getSystemPrivateKey($this->masterKeyId); + $decryptedMasterKey = $this->crypt->decryptPrivateKey($masterKey, $this->getMasterKeyPassword(), $this->masterKeyId); + $this->session->setPrivateKey($decryptedMasterKey); + } + + // after the encryption key is available we are ready to go + $this->session->setStatus(Session::INIT_SUCCESSFUL); } /** diff --git a/apps/encryption/lib/Migration/SetMasterKeyStatus.php b/apps/encryption/lib/Migration/SetMasterKeyStatus.php new file mode 100644 index 00000000000..a21d0acae24 --- /dev/null +++ b/apps/encryption/lib/Migration/SetMasterKeyStatus.php @@ -0,0 +1,77 @@ +<?php +/** + * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\Encryption\Migration; + + +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Class SetPasswordColumn + * + * @package OCA\Files_Sharing\Migration + */ +class SetMasterKeyStatus implements IRepairStep { + + + /** @var IConfig */ + private $config; + + + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * Returns the step's name + * + * @return string + * @since 9.1.0 + */ + public function getName() { + return 'Write default encryption module configuration to the database'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + if (!$this->shouldRun()) { + return; + } + + // if no config for the master key is set we set it explicitly to '0' in + // order not to break old installations because the default changed to '1'. + $configAlreadySet = $this->config->getAppValue('encryption', 'useMasterKey', false); + if ($configAlreadySet === false) { + $this->config->setAppValue('encryption', 'useMasterKey', '0'); + } + } + + protected function shouldRun() { + $appVersion = $this->config->getAppValue('encryption', 'installed_version', '0.0.0'); + return version_compare($appVersion, '2.0.0', '<'); + } + +} diff --git a/apps/encryption/lib/Util.php b/apps/encryption/lib/Util.php index 72afa68aad2..d6ae9bd7e5e 100644 --- a/apps/encryption/lib/Util.php +++ b/apps/encryption/lib/Util.php @@ -136,7 +136,7 @@ class Util { * @return bool */ public function isMasterKeyEnabled() { - $userMasterKey = $this->config->getAppValue('encryption', 'useMasterKey', '0'); + $userMasterKey = $this->config->getAppValue('encryption', 'useMasterKey', '1'); return ($userMasterKey === '1'); } diff --git a/apps/encryption/templates/settings-admin.php b/apps/encryption/templates/settings-admin.php index efe9c44ece7..c5f8d9f5536 100644 --- a/apps/encryption/templates/settings-admin.php +++ b/apps/encryption/templates/settings-admin.php @@ -7,7 +7,7 @@ style('encryption', 'settings-admin'); ?> <form id="ocDefaultEncryptionModule" class="sub-section"> <h3><?php p($l->t("Default encryption module")); ?></h3> - <?php if(!$_["initStatus"]): ?> + <?php if(!$_["initStatus"] && $_['masterKeyEnabled'] === false): ?> <?php p($l->t("Encryption app is enabled but your keys are not initialized, please log-out and log-in again")); ?> <?php else: ?> <p id="encryptHomeStorageSetting"> diff --git a/apps/encryption/tests/Controller/StatusControllerTest.php b/apps/encryption/tests/Controller/StatusControllerTest.php index c6c92e2aac2..ee0f7b2661c 100644 --- a/apps/encryption/tests/Controller/StatusControllerTest.php +++ b/apps/encryption/tests/Controller/StatusControllerTest.php @@ -27,6 +27,7 @@ namespace OCA\Encryption\Tests\Controller; use OCA\Encryption\Controller\StatusController; use OCA\Encryption\Session; +use OCP\Encryption\IManager; use OCP\IRequest; use Test\TestCase; @@ -41,6 +42,9 @@ class StatusControllerTest extends TestCase { /** @var \OCA\Encryption\Session | \PHPUnit_Framework_MockObject_MockObject */ protected $sessionMock; + /** @var IManager | \PHPUnit_Framework_MockObject_MockObject */ + protected $encryptionManagerMock; + /** @var StatusController */ protected $controller; @@ -59,11 +63,13 @@ class StatusControllerTest extends TestCase { ->will($this->returnCallback(function($message) { return $message; })); + $this->encryptionManagerMock = $this->createMock(IManager::class); $this->controller = new StatusController('encryptionTest', $this->requestMock, $this->l10nMock, - $this->sessionMock); + $this->sessionMock, + $this->encryptionManagerMock); } diff --git a/apps/encryption/tests/UtilTest.php b/apps/encryption/tests/UtilTest.php index d2f1d40e16d..40fc5537251 100644 --- a/apps/encryption/tests/UtilTest.php +++ b/apps/encryption/tests/UtilTest.php @@ -152,7 +152,7 @@ class UtilTest extends TestCase { */ public function testIsMasterKeyEnabled($value, $expect) { $this->configMock->expects($this->once())->method('getAppValue') - ->with('encryption', 'useMasterKey', '0')->willReturn($value); + ->with('encryption', 'useMasterKey', '1')->willReturn($value); $this->assertSame($expect, $this->instance->isMasterKeyEnabled() ); diff --git a/apps/federatedfilesharing/l10n/ru.js b/apps/federatedfilesharing/l10n/ru.js index 549c7e89241..6b204563e82 100644 --- a/apps/federatedfilesharing/l10n/ru.js +++ b/apps/federatedfilesharing/l10n/ru.js @@ -24,15 +24,15 @@ OC.L10N.register( "Storage not valid" : "Хранилище недоступно", "Federated Share successfully added" : "Федеративный общий ресурс успешно добавлен", "Couldn't add remote share" : "Невозможно добавить удалённый общий ресурс", - "Sharing %s failed, because this item is already shared with %s" : "Не удалось поделиться %s, пользователь %s уже имеет доступ к этому элементу", + "Sharing %s failed, because this item is already shared with %s" : "Не удалось поделиться «%s», пользователю%s уже предоставлен доступ к этому элементу", "Not allowed to create a federated share with the same user" : "Не допускается создание федеративного общего ресурса с тем же пользователем", "File is already shared with %s" : "Доступ к файлу уже предоставлен %s", - "Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate." : "Не удалось поделиться %s, не удалось найти %s, возможно, сервер не доступен или использует самозавернный сертификат.", + "Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate." : "Не удалось поделиться «%s», не удалось найти %s, возможно, сервер не доступен или использует само-подписанный сертификат.", "Could not find share" : "Не удалось найти общий ресурс", - "You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Вы получили \"%3$s\" в качестве удалённого ресурса из %1$s (от имени %2$s)", - "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Вы получили {share} в качестве удалённого ресурса от {user} (от имени {behalf})", - "You received \"%3$s\" as a remote share from %1$s" : "Вы получили \"%3$s\" в качестве удалённого ресурса из %1$s", - "You received {share} as a remote share from {user}" : "Вы получили {share} в качестве удалённого ресурса от {user}", + "You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Вы получили «%3$s» в качестве удалённого ресурса из %1$s (от имени %2$s)", + "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Вы получили «{share}» в качестве удалённого ресурса от {user} (от имени {behalf})", + "You received \"%3$s\" as a remote share from %1$s" : "Вы получили «%3$s» в качестве удалённого ресурса из %1$s", + "You received {share} as a remote share from {user}" : "Вы получили «{share}» в качестве удалённого ресурса от {user}", "Accept" : "Принять", "Decline" : "Отклонить", "Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Поделитесь со мной через мой #Nextcloud ID в федерации облачных хранилищ, смотрите %s", diff --git a/apps/federatedfilesharing/l10n/ru.json b/apps/federatedfilesharing/l10n/ru.json index e9389da5bbe..9a937895498 100644 --- a/apps/federatedfilesharing/l10n/ru.json +++ b/apps/federatedfilesharing/l10n/ru.json @@ -22,15 +22,15 @@ "Storage not valid" : "Хранилище недоступно", "Federated Share successfully added" : "Федеративный общий ресурс успешно добавлен", "Couldn't add remote share" : "Невозможно добавить удалённый общий ресурс", - "Sharing %s failed, because this item is already shared with %s" : "Не удалось поделиться %s, пользователь %s уже имеет доступ к этому элементу", + "Sharing %s failed, because this item is already shared with %s" : "Не удалось поделиться «%s», пользователю%s уже предоставлен доступ к этому элементу", "Not allowed to create a federated share with the same user" : "Не допускается создание федеративного общего ресурса с тем же пользователем", "File is already shared with %s" : "Доступ к файлу уже предоставлен %s", - "Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate." : "Не удалось поделиться %s, не удалось найти %s, возможно, сервер не доступен или использует самозавернный сертификат.", + "Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate." : "Не удалось поделиться «%s», не удалось найти %s, возможно, сервер не доступен или использует само-подписанный сертификат.", "Could not find share" : "Не удалось найти общий ресурс", - "You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Вы получили \"%3$s\" в качестве удалённого ресурса из %1$s (от имени %2$s)", - "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Вы получили {share} в качестве удалённого ресурса от {user} (от имени {behalf})", - "You received \"%3$s\" as a remote share from %1$s" : "Вы получили \"%3$s\" в качестве удалённого ресурса из %1$s", - "You received {share} as a remote share from {user}" : "Вы получили {share} в качестве удалённого ресурса от {user}", + "You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Вы получили «%3$s» в качестве удалённого ресурса из %1$s (от имени %2$s)", + "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Вы получили «{share}» в качестве удалённого ресурса от {user} (от имени {behalf})", + "You received \"%3$s\" as a remote share from %1$s" : "Вы получили «%3$s» в качестве удалённого ресурса из %1$s", + "You received {share} as a remote share from {user}" : "Вы получили «{share}» в качестве удалённого ресурса от {user}", "Accept" : "Принять", "Decline" : "Отклонить", "Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Поделитесь со мной через мой #Nextcloud ID в федерации облачных хранилищ, смотрите %s", diff --git a/apps/files_sharing/l10n/ru.js b/apps/files_sharing/l10n/ru.js index e3c71dfb6e4..06122cef13b 100644 --- a/apps/files_sharing/l10n/ru.js +++ b/apps/files_sharing/l10n/ru.js @@ -21,59 +21,59 @@ OC.L10N.register( "File shares" : "Обмен файлами", "Downloaded via public link" : "Скачано по общедоступной ссылке", "Downloaded by {email}" : "Скачано {email}", - "{file} downloaded via public link" : "{file} скачан по общедоступной ссылке", - "{email} downloaded {file}" : "{email} скачал {file}", - "Shared with group {group}" : "Поделился с группой {group}", - "Removed share for group {group}" : "Закрыт общий доступ для группы {group}", - "{actor} shared with group {group}" : "{actor} поделился с группой {group}", - "{actor} removed share for group {group}" : "{actor} закрыл общий доступ для группы {group}", - "You shared {file} with group {group}" : "Вы поделились {file} с группой {group}", - "You removed group {group} from {file}" : "Вы удалили группу {group} из {file}", - "{actor} shared {file} with group {group}" : "{actor} поделился {file} с группой {group}", - "{actor} removed group {group} from {file}" : "{actor} удалил группу {group} из {file}", + "{file} downloaded via public link" : "«{file}» скачан по общедоступной ссылке", + "{email} downloaded {file}" : "{email} скачал «{file}»", + "Shared with group {group}" : "Открыт доступ для группы «{group}»", + "Removed share for group {group}" : "Закрыт общий доступ для группы «{group}»", + "{actor} shared with group {group}" : "{actor} поделился с группой «{group}»", + "{actor} removed share for group {group}" : "{actor} закрыл общий доступ для группы «{group}»", + "You shared {file} with group {group}" : "Вы поделились «{file}» с группой «{group}»", + "You removed group {group} from {file}" : "Вы закрыли группе «{group}» доступ к «{file}»", + "{actor} shared {file} with group {group}" : "{actor} поделился «{file}» с группой «{group}»", + "{actor} removed group {group} from {file}" : "{actor} закрыл группе «{group}» общий доступ к «{file}»", "Shared as public link" : "Поделился общедоступной ссылкой", "Removed public link" : "Удалена общедоступная ссылка", "Public link expired" : "Срок действия общедоступнной ссылки закончился", "{actor} shared as public link" : "{actor} поделился общедоступной ссылкой", "{actor} removed public link" : "{actor} удалил общедоступной ссылку", "Public link of {actor} expired" : "Истёе срок действия общедоступной ссылки пользователя {actor}", - "You shared {file} as public link" : "Вы поделись {file} в виде общедоступной ссылки", - "You removed public link for {file}" : "Вы удалили общедоступную ссылку к {file}", - "Public link expired for {file}" : "Истёк срок действия общедоступной ссылки для файла {file}", - "{actor} shared {file} as public link" : "{actor} поделился {file} в виде общедоступной ссылки", - "{actor} removed public link for {file}" : "{actor} удалил общедоступную ссылку к {file}", - "Public link of {actor} for {file} expired" : "Истёк срок действия общедоступной ссылки пользователя {actor} на файл {file}", + "You shared {file} as public link" : "Вы поделись «{file}» в виде общедоступной ссылки", + "You removed public link for {file}" : "Вы удалили общедоступную ссылку к «{file}»", + "Public link expired for {file}" : "Истёк срок действия общедоступной ссылки для «{file}»", + "{actor} shared {file} as public link" : "{actor} открыл общий доступ к «{file}» в виде общедоступной ссылки", + "{actor} removed public link for {file}" : "{actor} удалил общедоступную ссылку к «{file}»", + "Public link of {actor} for {file} expired" : "Истёк срок действия общедоступной ссылки к «{file}», созданной {actor}", "{user} accepted the remote share" : "{user} принял удаленный общий ресурс", "{user} declined the remote share" : "{user} отклонил удаленный общий ресурс", - "You received a new remote share {file} from {user}" : "Вы получили новый удаленный общий ресурс {file} от {user}", - "{user} accepted the remote share of {file}" : "{user} принял удаленный общий ресурс {file}", - "{user} declined the remote share of {file}" : "{user} отклонил удаленный общий ресурс {file}", - "{user} unshared {file} from you" : "{user} закрыл для вас доступ к {file}", - "Shared with {user}" : "Поделился с {user}", + "You received a new remote share {file} from {user}" : "Вы получили от {user} новый общий ресурс с другого сервера «{file}» ", + "{user} accepted the remote share of {file}" : "{user} принял общий ресурс другого сервера «{file}»", + "{user} declined the remote share of {file}" : "{user} отклонил общий ресурс другого сервера «{file}»", + "{user} unshared {file} from you" : "{user} закрыл ваш доступ к «{file}»", + "Shared with {user}" : "Открыт общий доступ для {user}", "Removed share for {user}" : "Закрыт общий доступ для {user}", "{actor} shared with {user}" : "{actor} поделился с {user}", "{actor} removed share for {user}" : "{actor} закрыл общий доступ для {user}", "Shared by {actor}" : "Поделился через {actor}", "{actor} removed share" : "{actor} закрыл общий доступ", - "You shared {file} with {user}" : "Вы поделились {file} с {user}", - "You removed {user} from {file}" : "Вы удалили {user} из {file}", - "{actor} shared {file} with {user}" : "{actor} поделился {file} с {user}", - "{actor} removed {user} from {file}" : "{actor} удалил {user} из {file}", - "{actor} shared {file} with you" : "{actor} поделился {file} с вами", - "{actor} removed you from {file}" : "{actor} удалил вас из {file}", + "You shared {file} with {user}" : "Вы открыли доступ к «{file}» для {user}", + "You removed {user} from {file}" : "Вы закрыли общий доступ к «{file}» для {user}", + "{actor} shared {file} with {user}" : "{actor} открыл общий доступ к «{file}» для {user}", + "{actor} removed {user} from {file}" : "{actor} закрыл общий доступ к «{file}» для {user}", + "{actor} shared {file} with you" : "{actor} открыл вам общий доступ к «{file}»", + "{actor} removed you from {file}" : "{actor} закрыл вам общий доступ к «{file}»", "A file or folder shared by mail or by public link was <strong>downloaded</strong>" : "Файл или папка, которыми поделились по электронной почте или общедоступной ссылке, были <strong>скачаны</strong>", - "A file or folder was shared from <strong>another server</strong>" : "Файлом или каталогом поделились с <strong>удаленного сервера</strong>", + "A file or folder was shared from <strong>another server</strong>" : "Общий доступ к файлу или каталогу был открыт <strong>с другого сервера</strong>", "A file or folder has been <strong>shared</strong>" : "<strong>Опубликован</strong> файл или каталог", "Wrong share ID, share doesn't exist" : "Неверный идентификатор публикации, публикация не существует", "could not delete share" : "Не удалось удалить общий ресурс", - "Could not delete share" : "Не удалось удалить публикацию", + "Could not delete share" : "Не удалось удалить общий ресурс", "Please specify a file or folder path" : "Пожалуйста, укажите путь к файлу или каталогу", "Wrong path, file/folder doesn't exist" : "Неверный путь, файл/каталог не существует", "Could not create share" : "Не удалось создать общий ресурс", - "invalid permissions" : "Неверные права", - "Please specify a valid user" : "Пожалуйста, укажите допустимого пользователя", + "invalid permissions" : "неверные права", + "Please specify a valid user" : "Укажите верного пользователя", "Group sharing is disabled by the administrator" : "Общий доступ для групп отключён администратором", - "Please specify a valid group" : "Пожалуйста, укажите допустимую группу", + "Please specify a valid group" : "Укажите верную группу", "Public link sharing is disabled by the administrator" : "Публикация через общедоступные ссылки отключена администратором", "Public upload disabled by the administrator" : "Загрузка в общедоступную публикацию запрещена администратором", "Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в папки с общим доступом", @@ -81,7 +81,7 @@ OC.L10N.register( "Sharing %s failed because the back end does not allow shares from type %s" : "Не удалось поделиться %s, поскольку механизм удалённого обмена не разрешает публикации типа %s", "You cannot share to a Circle if the app is not enabled" : "Вы не можите поделиться через приложение Круг, если это приложение не включено", "Please specify a valid circle" : "Укажите правильный круг", - "Unknown share type" : "Предоставление доступа неизвестного типа", + "Unknown share type" : "Общий доступ неизвестного типа", "Not a directory" : "Это не каталог", "Could not lock path" : "Не удалось заблокировать путь", "Wrong or no update parameter given" : "Параметр для изменения неправилен или не задан", diff --git a/apps/files_sharing/l10n/ru.json b/apps/files_sharing/l10n/ru.json index b0def25d616..00a0cbf7da9 100644 --- a/apps/files_sharing/l10n/ru.json +++ b/apps/files_sharing/l10n/ru.json @@ -19,59 +19,59 @@ "File shares" : "Обмен файлами", "Downloaded via public link" : "Скачано по общедоступной ссылке", "Downloaded by {email}" : "Скачано {email}", - "{file} downloaded via public link" : "{file} скачан по общедоступной ссылке", - "{email} downloaded {file}" : "{email} скачал {file}", - "Shared with group {group}" : "Поделился с группой {group}", - "Removed share for group {group}" : "Закрыт общий доступ для группы {group}", - "{actor} shared with group {group}" : "{actor} поделился с группой {group}", - "{actor} removed share for group {group}" : "{actor} закрыл общий доступ для группы {group}", - "You shared {file} with group {group}" : "Вы поделились {file} с группой {group}", - "You removed group {group} from {file}" : "Вы удалили группу {group} из {file}", - "{actor} shared {file} with group {group}" : "{actor} поделился {file} с группой {group}", - "{actor} removed group {group} from {file}" : "{actor} удалил группу {group} из {file}", + "{file} downloaded via public link" : "«{file}» скачан по общедоступной ссылке", + "{email} downloaded {file}" : "{email} скачал «{file}»", + "Shared with group {group}" : "Открыт доступ для группы «{group}»", + "Removed share for group {group}" : "Закрыт общий доступ для группы «{group}»", + "{actor} shared with group {group}" : "{actor} поделился с группой «{group}»", + "{actor} removed share for group {group}" : "{actor} закрыл общий доступ для группы «{group}»", + "You shared {file} with group {group}" : "Вы поделились «{file}» с группой «{group}»", + "You removed group {group} from {file}" : "Вы закрыли группе «{group}» доступ к «{file}»", + "{actor} shared {file} with group {group}" : "{actor} поделился «{file}» с группой «{group}»", + "{actor} removed group {group} from {file}" : "{actor} закрыл группе «{group}» общий доступ к «{file}»", "Shared as public link" : "Поделился общедоступной ссылкой", "Removed public link" : "Удалена общедоступная ссылка", "Public link expired" : "Срок действия общедоступнной ссылки закончился", "{actor} shared as public link" : "{actor} поделился общедоступной ссылкой", "{actor} removed public link" : "{actor} удалил общедоступной ссылку", "Public link of {actor} expired" : "Истёе срок действия общедоступной ссылки пользователя {actor}", - "You shared {file} as public link" : "Вы поделись {file} в виде общедоступной ссылки", - "You removed public link for {file}" : "Вы удалили общедоступную ссылку к {file}", - "Public link expired for {file}" : "Истёк срок действия общедоступной ссылки для файла {file}", - "{actor} shared {file} as public link" : "{actor} поделился {file} в виде общедоступной ссылки", - "{actor} removed public link for {file}" : "{actor} удалил общедоступную ссылку к {file}", - "Public link of {actor} for {file} expired" : "Истёк срок действия общедоступной ссылки пользователя {actor} на файл {file}", + "You shared {file} as public link" : "Вы поделись «{file}» в виде общедоступной ссылки", + "You removed public link for {file}" : "Вы удалили общедоступную ссылку к «{file}»", + "Public link expired for {file}" : "Истёк срок действия общедоступной ссылки для «{file}»", + "{actor} shared {file} as public link" : "{actor} открыл общий доступ к «{file}» в виде общедоступной ссылки", + "{actor} removed public link for {file}" : "{actor} удалил общедоступную ссылку к «{file}»", + "Public link of {actor} for {file} expired" : "Истёк срок действия общедоступной ссылки к «{file}», созданной {actor}", "{user} accepted the remote share" : "{user} принял удаленный общий ресурс", "{user} declined the remote share" : "{user} отклонил удаленный общий ресурс", - "You received a new remote share {file} from {user}" : "Вы получили новый удаленный общий ресурс {file} от {user}", - "{user} accepted the remote share of {file}" : "{user} принял удаленный общий ресурс {file}", - "{user} declined the remote share of {file}" : "{user} отклонил удаленный общий ресурс {file}", - "{user} unshared {file} from you" : "{user} закрыл для вас доступ к {file}", - "Shared with {user}" : "Поделился с {user}", + "You received a new remote share {file} from {user}" : "Вы получили от {user} новый общий ресурс с другого сервера «{file}» ", + "{user} accepted the remote share of {file}" : "{user} принял общий ресурс другого сервера «{file}»", + "{user} declined the remote share of {file}" : "{user} отклонил общий ресурс другого сервера «{file}»", + "{user} unshared {file} from you" : "{user} закрыл ваш доступ к «{file}»", + "Shared with {user}" : "Открыт общий доступ для {user}", "Removed share for {user}" : "Закрыт общий доступ для {user}", "{actor} shared with {user}" : "{actor} поделился с {user}", "{actor} removed share for {user}" : "{actor} закрыл общий доступ для {user}", "Shared by {actor}" : "Поделился через {actor}", "{actor} removed share" : "{actor} закрыл общий доступ", - "You shared {file} with {user}" : "Вы поделились {file} с {user}", - "You removed {user} from {file}" : "Вы удалили {user} из {file}", - "{actor} shared {file} with {user}" : "{actor} поделился {file} с {user}", - "{actor} removed {user} from {file}" : "{actor} удалил {user} из {file}", - "{actor} shared {file} with you" : "{actor} поделился {file} с вами", - "{actor} removed you from {file}" : "{actor} удалил вас из {file}", + "You shared {file} with {user}" : "Вы открыли доступ к «{file}» для {user}", + "You removed {user} from {file}" : "Вы закрыли общий доступ к «{file}» для {user}", + "{actor} shared {file} with {user}" : "{actor} открыл общий доступ к «{file}» для {user}", + "{actor} removed {user} from {file}" : "{actor} закрыл общий доступ к «{file}» для {user}", + "{actor} shared {file} with you" : "{actor} открыл вам общий доступ к «{file}»", + "{actor} removed you from {file}" : "{actor} закрыл вам общий доступ к «{file}»", "A file or folder shared by mail or by public link was <strong>downloaded</strong>" : "Файл или папка, которыми поделились по электронной почте или общедоступной ссылке, были <strong>скачаны</strong>", - "A file or folder was shared from <strong>another server</strong>" : "Файлом или каталогом поделились с <strong>удаленного сервера</strong>", + "A file or folder was shared from <strong>another server</strong>" : "Общий доступ к файлу или каталогу был открыт <strong>с другого сервера</strong>", "A file or folder has been <strong>shared</strong>" : "<strong>Опубликован</strong> файл или каталог", "Wrong share ID, share doesn't exist" : "Неверный идентификатор публикации, публикация не существует", "could not delete share" : "Не удалось удалить общий ресурс", - "Could not delete share" : "Не удалось удалить публикацию", + "Could not delete share" : "Не удалось удалить общий ресурс", "Please specify a file or folder path" : "Пожалуйста, укажите путь к файлу или каталогу", "Wrong path, file/folder doesn't exist" : "Неверный путь, файл/каталог не существует", "Could not create share" : "Не удалось создать общий ресурс", - "invalid permissions" : "Неверные права", - "Please specify a valid user" : "Пожалуйста, укажите допустимого пользователя", + "invalid permissions" : "неверные права", + "Please specify a valid user" : "Укажите верного пользователя", "Group sharing is disabled by the administrator" : "Общий доступ для групп отключён администратором", - "Please specify a valid group" : "Пожалуйста, укажите допустимую группу", + "Please specify a valid group" : "Укажите верную группу", "Public link sharing is disabled by the administrator" : "Публикация через общедоступные ссылки отключена администратором", "Public upload disabled by the administrator" : "Загрузка в общедоступную публикацию запрещена администратором", "Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в папки с общим доступом", @@ -79,7 +79,7 @@ "Sharing %s failed because the back end does not allow shares from type %s" : "Не удалось поделиться %s, поскольку механизм удалённого обмена не разрешает публикации типа %s", "You cannot share to a Circle if the app is not enabled" : "Вы не можите поделиться через приложение Круг, если это приложение не включено", "Please specify a valid circle" : "Укажите правильный круг", - "Unknown share type" : "Предоставление доступа неизвестного типа", + "Unknown share type" : "Общий доступ неизвестного типа", "Not a directory" : "Это не каталог", "Could not lock path" : "Не удалось заблокировать путь", "Wrong or no update parameter given" : "Параметр для изменения неправилен или не задан", diff --git a/apps/files_sharing/tests/EncryptedSizePropagationTest.php b/apps/files_sharing/tests/EncryptedSizePropagationTest.php index 6b6ed2cd73e..38bbf12177c 100644 --- a/apps/files_sharing/tests/EncryptedSizePropagationTest.php +++ b/apps/files_sharing/tests/EncryptedSizePropagationTest.php @@ -36,8 +36,10 @@ class EncryptedSizePropagationTest extends SizePropagationTest { $this->createUser($name, $password); $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); + $this->config->setAppValue('encryption', 'useMasterKey', '0'); $this->setupForUser($name, $password); $this->loginWithEncryption($name); return new View('/' . $name . '/files'); } + } diff --git a/apps/sharebymail/l10n/zh_CN.js b/apps/sharebymail/l10n/zh_CN.js index 7586c87a060..20aaf8b0bce 100644 --- a/apps/sharebymail/l10n/zh_CN.js +++ b/apps/sharebymail/l10n/zh_CN.js @@ -24,9 +24,12 @@ OC.L10N.register( "Click the button below to open it." : "点击下面的按钮打开它。", "Open »%s«" : "打开 »%s«", "%s via %s" : "%s通过%s", + "Password to access »%s« shared to you by %s" : "使用密码访问»%s«由%s分享", "%s shared »%s« with you.\nYou should have already received a separate mail with a link to access it.\n" : "%s与您共享了%s\n访问链接已另外以邮件方式发送到您的邮箱\n", "%s shared »%s« with you. You should have already received a separate mail with a link to access it." : "%s与您共享了%s。访问链接已另外以邮件方式发送到您的邮箱。", "Password to access »%s«" : "访问 »%s« 的密码", + "It is protected with the following password: %s" : "已被已下密码保护:%s", + "Password to access »%s« shared with %s" : "使用密码访问»%s«与%s分享", "This is the password: %s" : "这是密码: %s", "You can choose a different password at any time in the share dialog." : "您可以随时在共享对话框中选择不同的密码。", "Could not find share" : "没有发现共享", diff --git a/apps/sharebymail/l10n/zh_CN.json b/apps/sharebymail/l10n/zh_CN.json index 89be7076f6c..756da2326a1 100644 --- a/apps/sharebymail/l10n/zh_CN.json +++ b/apps/sharebymail/l10n/zh_CN.json @@ -22,9 +22,12 @@ "Click the button below to open it." : "点击下面的按钮打开它。", "Open »%s«" : "打开 »%s«", "%s via %s" : "%s通过%s", + "Password to access »%s« shared to you by %s" : "使用密码访问»%s«由%s分享", "%s shared »%s« with you.\nYou should have already received a separate mail with a link to access it.\n" : "%s与您共享了%s\n访问链接已另外以邮件方式发送到您的邮箱\n", "%s shared »%s« with you. You should have already received a separate mail with a link to access it." : "%s与您共享了%s。访问链接已另外以邮件方式发送到您的邮箱。", "Password to access »%s«" : "访问 »%s« 的密码", + "It is protected with the following password: %s" : "已被已下密码保护:%s", + "Password to access »%s« shared with %s" : "使用密码访问»%s«与%s分享", "This is the password: %s" : "这是密码: %s", "You can choose a different password at any time in the share dialog." : "您可以随时在共享对话框中选择不同的密码。", "Could not find share" : "没有发现共享", diff --git a/apps/updatenotification/l10n/de.js b/apps/updatenotification/l10n/de.js index 1a7014d6441..00977b2c5fe 100644 --- a/apps/updatenotification/l10n/de.js +++ b/apps/updatenotification/l10n/de.js @@ -18,6 +18,7 @@ OC.L10N.register( "Checked on %s" : "Geprüft am %s", "Update channel:" : "Update-Kanal:", "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Es kann immer auf eine neuere Version / experimentellen Kanal aktualisiert werden. Allerdings kann kein Downgrade auf einen stabileren Kanal durchgeführt werden.", + "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Nach Veröffentlichung einer neuen Version kann es einige Zeit dauern bis diese hier erscheint. Die neuen Versionen verteilen sich beim Ausrollen im Laufe der Zeit auf die Benutzer. Manchmal werden Versionen übersprungen, wenn Probleme gefunden wurden.", "Notify members of the following groups about available updates:" : "Informiere die Mitglieder der folgenden Gruppen über verfügbare Updates:", "Only notification for app updates are available." : "Benachrichtigungen sind nur für Aktualisierungen von Apps verfügbar.", "The selected update channel makes dedicated notifications for the server obsolete." : "Der gewählte Aktualisierungskanal macht dedizierte Benachrichtigungen für Server Aktualisierungen obsolet.", diff --git a/apps/updatenotification/l10n/de.json b/apps/updatenotification/l10n/de.json index 86387b7ff5a..e391a52ab93 100644 --- a/apps/updatenotification/l10n/de.json +++ b/apps/updatenotification/l10n/de.json @@ -16,6 +16,7 @@ "Checked on %s" : "Geprüft am %s", "Update channel:" : "Update-Kanal:", "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Es kann immer auf eine neuere Version / experimentellen Kanal aktualisiert werden. Allerdings kann kein Downgrade auf einen stabileren Kanal durchgeführt werden.", + "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Nach Veröffentlichung einer neuen Version kann es einige Zeit dauern bis diese hier erscheint. Die neuen Versionen verteilen sich beim Ausrollen im Laufe der Zeit auf die Benutzer. Manchmal werden Versionen übersprungen, wenn Probleme gefunden wurden.", "Notify members of the following groups about available updates:" : "Informiere die Mitglieder der folgenden Gruppen über verfügbare Updates:", "Only notification for app updates are available." : "Benachrichtigungen sind nur für Aktualisierungen von Apps verfügbar.", "The selected update channel makes dedicated notifications for the server obsolete." : "Der gewählte Aktualisierungskanal macht dedizierte Benachrichtigungen für Server Aktualisierungen obsolet.", diff --git a/apps/updatenotification/l10n/de_DE.js b/apps/updatenotification/l10n/de_DE.js index 4fbfa7b6b8d..9c23260216b 100644 --- a/apps/updatenotification/l10n/de_DE.js +++ b/apps/updatenotification/l10n/de_DE.js @@ -18,6 +18,7 @@ OC.L10N.register( "Checked on %s" : "Überprüft am %s", "Update channel:" : "Update-Kanal:", "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Sie können immer auf eine neuere Version / experimentellen Kanal updaten, aber kein Downgrade auf einen stabileren Kanal durchführen.", + "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Nach Veröffentlichung einer neuen Version kann es einige Zeit dauern bis diese hier erscheint. Die neuen Versionen verteilen sich beim Ausrollen im Laufe der Zeit auf die Benutzer. Manchmal werden Versionen übersprungen, wenn Probleme gefunden wurden.", "Notify members of the following groups about available updates:" : "Informieren Sie die Mitglieder der folgenden Gruppen über verfügbare Updates:", "Only notification for app updates are available." : "Benachrichtigungen sind nur für Aktualisierungen von Apps verfügbar.", "The selected update channel makes dedicated notifications for the server obsolete." : "Der gewählte Aktualisierungskanal macht dedizierte Benachrichtigungen für Server Aktualisierungen obsolet.", diff --git a/apps/updatenotification/l10n/de_DE.json b/apps/updatenotification/l10n/de_DE.json index 9a3eb8c4686..7df9281891b 100644 --- a/apps/updatenotification/l10n/de_DE.json +++ b/apps/updatenotification/l10n/de_DE.json @@ -16,6 +16,7 @@ "Checked on %s" : "Überprüft am %s", "Update channel:" : "Update-Kanal:", "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Sie können immer auf eine neuere Version / experimentellen Kanal updaten, aber kein Downgrade auf einen stabileren Kanal durchführen.", + "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Nach Veröffentlichung einer neuen Version kann es einige Zeit dauern bis diese hier erscheint. Die neuen Versionen verteilen sich beim Ausrollen im Laufe der Zeit auf die Benutzer. Manchmal werden Versionen übersprungen, wenn Probleme gefunden wurden.", "Notify members of the following groups about available updates:" : "Informieren Sie die Mitglieder der folgenden Gruppen über verfügbare Updates:", "Only notification for app updates are available." : "Benachrichtigungen sind nur für Aktualisierungen von Apps verfügbar.", "The selected update channel makes dedicated notifications for the server obsolete." : "Der gewählte Aktualisierungskanal macht dedizierte Benachrichtigungen für Server Aktualisierungen obsolet.", diff --git a/apps/updatenotification/l10n/ru.js b/apps/updatenotification/l10n/ru.js index fdcc6c91944..e523c19689a 100644 --- a/apps/updatenotification/l10n/ru.js +++ b/apps/updatenotification/l10n/ru.js @@ -18,6 +18,7 @@ OC.L10N.register( "Checked on %s" : "Проверено %s", "Update channel:" : "Канал обновлений:", "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Вы всегда можете переключиться на экспериментальный канал обновлений для получения новейших версий. Но учтите, что вы не сможете переключиться обратно на канал обновлений для стабильных версий.", + "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Обратите внимание, что от момента выпуска новой версии до её появления здесь может пройти некоторое время. Мы растягиваем во времени распространение новых версий и иногда, при обнаружении проблем, пропускаем версию.", "Notify members of the following groups about available updates:" : "Уведомить членов следующих групп о наличии доступных обновлений:", "Only notification for app updates are available." : "Только уведомления об обновлении приложений доступны.", "The selected update channel makes dedicated notifications for the server obsolete." : "Выбранный канал обновлений высылает специальные уведомления, если сервер устарел.", diff --git a/apps/updatenotification/l10n/ru.json b/apps/updatenotification/l10n/ru.json index ec54e69b26d..0f4ceac7c57 100644 --- a/apps/updatenotification/l10n/ru.json +++ b/apps/updatenotification/l10n/ru.json @@ -16,6 +16,7 @@ "Checked on %s" : "Проверено %s", "Update channel:" : "Канал обновлений:", "You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel." : "Вы всегда можете переключиться на экспериментальный канал обновлений для получения новейших версий. Но учтите, что вы не сможете переключиться обратно на канал обновлений для стабильных версий.", + "Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found." : "Обратите внимание, что от момента выпуска новой версии до её появления здесь может пройти некоторое время. Мы растягиваем во времени распространение новых версий и иногда, при обнаружении проблем, пропускаем версию.", "Notify members of the following groups about available updates:" : "Уведомить членов следующих групп о наличии доступных обновлений:", "Only notification for app updates are available." : "Только уведомления об обновлении приложений доступны.", "The selected update channel makes dedicated notifications for the server obsolete." : "Выбранный канал обновлений высылает специальные уведомления, если сервер устарел.", diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index ee1dc08021c..5a8a921f219 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -751,6 +751,7 @@ return array( 'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => $baseDir . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php', 'OC\\Repair\\NC12\\InstallCoreBundle' => $baseDir . '/lib/private/Repair/NC12/InstallCoreBundle.php', 'OC\\Repair\\NC12\\UpdateLanguageCodes' => $baseDir . '/lib/private/Repair/NC12/UpdateLanguageCodes.php', + 'OC\\Repair\\NC13\\RepairInvalidPaths' => $baseDir . '/lib/private/Repair/NC13/RepairInvalidPaths.php', 'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php', 'OC\\Repair\\Owncloud\\SaveAccountsTableData' => $baseDir . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php', 'OC\\Repair\\RemoveRootShares' => $baseDir . '/lib/private/Repair/RemoveRootShares.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 7b133d54a20..b4aa6cc322b 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -781,6 +781,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php', 'OC\\Repair\\NC12\\InstallCoreBundle' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/InstallCoreBundle.php', 'OC\\Repair\\NC12\\UpdateLanguageCodes' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/UpdateLanguageCodes.php', + 'OC\\Repair\\NC13\\RepairInvalidPaths' => __DIR__ . '/../../..' . '/lib/private/Repair/NC13/RepairInvalidPaths.php', 'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php', 'OC\\Repair\\Owncloud\\SaveAccountsTableData' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php', 'OC\\Repair\\RemoveRootShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RemoveRootShares.php', diff --git a/lib/l10n/zh_CN.js b/lib/l10n/zh_CN.js index dd62ec5784e..aee9d512d9a 100644 --- a/lib/l10n/zh_CN.js +++ b/lib/l10n/zh_CN.js @@ -4,6 +4,7 @@ OC.L10N.register( "Cannot write into \"config\" directory!" : "无法写入 \"config\" 目录!ond", "This can usually be fixed by giving the webserver write access to the config directory" : "您可以设置 Web 服务器对 config 目录的写权限修复这个问题", "See %s" : "查看 %s", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "这个通常可以通过赋予写入权限到 config 目录来修复。查看:%s", "The files of the app %$1s were not replaced correctly. Make sure it is a version compatible with the server." : "应用 %$1s 的文件替换不正确. 请确认版本与当前服务器兼容.", "Sample configuration detected" : "示例配置检测", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用. 这可能会破坏您的安装. 在对 config.php 进行修改之前请先阅读相关文档.", @@ -11,6 +12,9 @@ OC.L10N.register( "%1$s, %2$s and %3$s" : "%1$s, %2$s 和 %3$s", "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s 和 %4$s", "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s 和 %5$s", + "Enterprise bundle" : "企业捆绑包", + "Groupware bundle" : "群组捆绑包", + "Social sharing bundle" : "社交分享捆绑包", "PHP %s or higher is required." : "要求 PHP 版本 %s 或者更高。", "PHP with a version lower than %s is required." : "需要版本低于 %s 的PHP.", "%sbit or higher PHP required." : "需要 %s 或更高版本的 PHP", @@ -35,6 +39,7 @@ OC.L10N.register( "_%n hour ago_::_%n hours ago_" : ["%n 小时前"], "_%n minute ago_::_%n minutes ago_" : ["%n 分钟前"], "seconds ago" : "几秒前", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "模块:%s不存在。请在 App 设置中开启或联系管理员。", "File name is a reserved word" : "文件名包含敏感字符", "File name contains at least one invalid character" : "文件名中存在至少一个非法字符", "File name is too long" : "文件名过长", @@ -56,6 +61,10 @@ OC.L10N.register( "Encryption" : "加密", "Additional settings" : "其他设置", "Tips & tricks" : "小提示", + "Personal info" : "个人信息", + "Sync clients" : "同步客户", + "Unlimited" : "无限制", + "__language_name__" : "简体中文", "Verifying" : "验证", "Verifying …" : "验证...", "Verify" : "验证", @@ -106,7 +115,12 @@ OC.L10N.register( "Sharing %s failed, because resharing is not allowed" : "分享 %s 失败, 因为不允许二次共享", "Sharing %s failed, because the sharing backend for %s could not find its source" : "分享 %s 失败, 因为无法找到 %s 分享后端的来源", "Sharing %s failed, because the file could not be found in the file cache" : "分享 %s 失败, 因为文件缓存中找不到该文件", + "Can’t increase permissions of %s" : "无法增加%s的权限。", + "Files can’t be shared with delete permissions" : "无法分享有删除权限的文件", + "Files can’t be shared with create permissions" : "无法分享有创建权限的文件", "Expiration date is in the past" : "到期日期已过.", + "Can’t set expiration date more than %s days in the future" : "无法将过期日期设置为超过 %s 天.", + "The requested share does not exist anymore" : "当前请求的共享已经不存在", "Could not find category \"%s\"" : "无法找到分类 \"%s\"", "Sunday" : "星期日", "Monday" : "星期一", @@ -174,7 +188,10 @@ OC.L10N.register( "No database drivers (sqlite, mysql, or postgresql) installed." : "没有安装数据库驱动 (SQLite、MySQL 或 PostgreSQL)。", "Cannot write into \"config\" directory" : "无法写入“config”目录", "Cannot write into \"apps\" directory" : "无法写入“apps”目录", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "这个通常可以通过赋予 apps 目录写入权限或者在 config 文件中关闭 AppStore 来修复。详情:%s", "Cannot create \"data\" directory" : "无法创建“data”目录 ", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "这个通常可以通过赋予根目录写入权限来修复。查看:%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "权限通常可以通过赋予根目录写入权限来修复。查看:%s。", "Setting locale to %s failed" : "设置语言为 %s 失败", "Please install one of these locales on your system and restart your webserver." : "请在您的系统中安装下述一种语言并重启 Web 服务器.", "Please ask your server administrator to install the module." : "请联系服务器管理员安装模块.", @@ -196,6 +213,7 @@ OC.L10N.register( "Your data directory must be an absolute path" : "您的数据目录必须是绝对路径", "Check the value of \"datadirectory\" in your configuration" : "请检查配置文件中 \"datadirectory\" 的值", "Your data directory is invalid" : "您的数据目录无效", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "请确定在根目录下有一个名为\".ocdata\"的文件。", "Could not obtain lock type %d on \"%s\"." : "无法在 \"%s\" 上获取锁类型 %d.", "Storage unauthorized. %s" : "存储认证失败. %s", "Storage incomplete configuration. %s" : "存储未完成配置. %s", diff --git a/lib/l10n/zh_CN.json b/lib/l10n/zh_CN.json index 7c73de1bc4f..40af580cd2d 100644 --- a/lib/l10n/zh_CN.json +++ b/lib/l10n/zh_CN.json @@ -2,6 +2,7 @@ "Cannot write into \"config\" directory!" : "无法写入 \"config\" 目录!ond", "This can usually be fixed by giving the webserver write access to the config directory" : "您可以设置 Web 服务器对 config 目录的写权限修复这个问题", "See %s" : "查看 %s", + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "这个通常可以通过赋予写入权限到 config 目录来修复。查看:%s", "The files of the app %$1s were not replaced correctly. Make sure it is a version compatible with the server." : "应用 %$1s 的文件替换不正确. 请确认版本与当前服务器兼容.", "Sample configuration detected" : "示例配置检测", "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "您似乎直接把 config.php 的样例文件直接复制使用. 这可能会破坏您的安装. 在对 config.php 进行修改之前请先阅读相关文档.", @@ -9,6 +10,9 @@ "%1$s, %2$s and %3$s" : "%1$s, %2$s 和 %3$s", "%1$s, %2$s, %3$s and %4$s" : "%1$s, %2$s, %3$s 和 %4$s", "%1$s, %2$s, %3$s, %4$s and %5$s" : "%1$s, %2$s, %3$s, %4$s 和 %5$s", + "Enterprise bundle" : "企业捆绑包", + "Groupware bundle" : "群组捆绑包", + "Social sharing bundle" : "社交分享捆绑包", "PHP %s or higher is required." : "要求 PHP 版本 %s 或者更高。", "PHP with a version lower than %s is required." : "需要版本低于 %s 的PHP.", "%sbit or higher PHP required." : "需要 %s 或更高版本的 PHP", @@ -33,6 +37,7 @@ "_%n hour ago_::_%n hours ago_" : ["%n 小时前"], "_%n minute ago_::_%n minutes ago_" : ["%n 分钟前"], "seconds ago" : "几秒前", + "Module with ID: %s does not exist. Please enable it in your apps settings or contact your administrator." : "模块:%s不存在。请在 App 设置中开启或联系管理员。", "File name is a reserved word" : "文件名包含敏感字符", "File name contains at least one invalid character" : "文件名中存在至少一个非法字符", "File name is too long" : "文件名过长", @@ -54,6 +59,10 @@ "Encryption" : "加密", "Additional settings" : "其他设置", "Tips & tricks" : "小提示", + "Personal info" : "个人信息", + "Sync clients" : "同步客户", + "Unlimited" : "无限制", + "__language_name__" : "简体中文", "Verifying" : "验证", "Verifying …" : "验证...", "Verify" : "验证", @@ -104,7 +113,12 @@ "Sharing %s failed, because resharing is not allowed" : "分享 %s 失败, 因为不允许二次共享", "Sharing %s failed, because the sharing backend for %s could not find its source" : "分享 %s 失败, 因为无法找到 %s 分享后端的来源", "Sharing %s failed, because the file could not be found in the file cache" : "分享 %s 失败, 因为文件缓存中找不到该文件", + "Can’t increase permissions of %s" : "无法增加%s的权限。", + "Files can’t be shared with delete permissions" : "无法分享有删除权限的文件", + "Files can’t be shared with create permissions" : "无法分享有创建权限的文件", "Expiration date is in the past" : "到期日期已过.", + "Can’t set expiration date more than %s days in the future" : "无法将过期日期设置为超过 %s 天.", + "The requested share does not exist anymore" : "当前请求的共享已经不存在", "Could not find category \"%s\"" : "无法找到分类 \"%s\"", "Sunday" : "星期日", "Monday" : "星期一", @@ -172,7 +186,10 @@ "No database drivers (sqlite, mysql, or postgresql) installed." : "没有安装数据库驱动 (SQLite、MySQL 或 PostgreSQL)。", "Cannot write into \"config\" directory" : "无法写入“config”目录", "Cannot write into \"apps\" directory" : "无法写入“apps”目录", + "This can usually be fixed by giving the webserver write access to the apps directory or disabling the appstore in the config file. See %s" : "这个通常可以通过赋予 apps 目录写入权限或者在 config 文件中关闭 AppStore 来修复。详情:%s", "Cannot create \"data\" directory" : "无法创建“data”目录 ", + "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "这个通常可以通过赋予根目录写入权限来修复。查看:%s", + "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "权限通常可以通过赋予根目录写入权限来修复。查看:%s。", "Setting locale to %s failed" : "设置语言为 %s 失败", "Please install one of these locales on your system and restart your webserver." : "请在您的系统中安装下述一种语言并重启 Web 服务器.", "Please ask your server administrator to install the module." : "请联系服务器管理员安装模块.", @@ -194,6 +211,7 @@ "Your data directory must be an absolute path" : "您的数据目录必须是绝对路径", "Check the value of \"datadirectory\" in your configuration" : "请检查配置文件中 \"datadirectory\" 的值", "Your data directory is invalid" : "您的数据目录无效", + "Ensure there is a file called \".ocdata\" in the root of the data directory." : "请确定在根目录下有一个名为\".ocdata\"的文件。", "Could not obtain lock type %d on \"%s\"." : "无法在 \"%s\" 上获取锁类型 %d.", "Storage unauthorized. %s" : "存储认证失败. %s", "Storage incomplete configuration. %s" : "存储未完成配置. %s", diff --git a/lib/private/Encryption/Update.php b/lib/private/Encryption/Update.php index ad40183767b..94d64b73504 100644 --- a/lib/private/Encryption/Update.php +++ b/lib/private/Encryption/Update.php @@ -168,6 +168,14 @@ class Update { */ public function update($path) { + $encryptionModule = $this->encryptionManager->getEncryptionModule(); + + // if the encryption module doesn't encrypt the files on a per-user basis + // we have nothing to do here. + if ($encryptionModule->needDetailedAccessList() === false) { + return; + } + // if a folder was shared, get a list of all (sub-)folders if ($this->view->is_dir($path)) { $allFiles = $this->util->getAllFiles($path); @@ -175,7 +183,7 @@ class Update { $allFiles = array($path); } - $encryptionModule = $this->encryptionManager->getEncryptionModule(); + foreach ($allFiles as $file) { $usersSharing = $this->file->getAccessList($file); diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index ebab20fbaed..8f12ca77ee6 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -94,7 +94,7 @@ class CacheJail extends CacheWrapper { * get the stored metadata of a file or folder * * @param string /int $file - * @return array|false + * @return ICacheEntry|false */ public function get($file) { if (is_string($file) or $file == '') { @@ -176,6 +176,16 @@ class CacheJail extends CacheWrapper { } /** + * Get the storage id and path needed for a move + * + * @param string $path + * @return array [$storageId, $internalPath] + */ + protected function getMoveInfo($path) { + return [$this->getNumericStorageId(), $this->getSourcePath($path)]; + } + + /** * remove all entries for files that are stored on the storage from the cache */ public function clear() { diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index d1f68696848..b68917ce76e 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -254,7 +254,10 @@ class Encryption extends Wrapper { $sharePath = dirname($sharePath); } - $accessList = $this->file->getAccessList($sharePath); + $accessList = []; + if ($this->encryptionModule->needDetailedAccessList()) { + $accessList = $this->file->getAccessList($sharePath); + } $this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList); if ( diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php index 16552005931..b854b44b340 100644 --- a/lib/private/NavigationManager.php +++ b/lib/private/NavigationManager.php @@ -216,7 +216,18 @@ class NavigationManager implements INavigationManager { if ($this->appManager === 'null') { return; } - foreach ($this->appManager->getInstalledApps() as $app) { + + if ($this->userSession->isLoggedIn()) { + $apps = $this->appManager->getEnabledAppsForUser($this->userSession->getUser()); + } else { + $apps = $this->appManager->getInstalledApps(); + } + + foreach ($apps as $app) { + if (!$this->userSession->isLoggedIn() && !$this->appManager->isEnabledForUser($app, $this->userSession->getUser())) { + continue; + } + // load plugins and collections from info.xml $info = $this->appManager->getAppInfo($app); if (empty($info['navigations'])) { diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 4d14bf2550c..dae328e6340 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -42,6 +42,7 @@ use OC\Repair\NC12\UpdateLanguageCodes; use OC\Repair\OldGroupMembershipShares; use OC\Repair\Owncloud\SaveAccountsTableData; use OC\Repair\RemoveRootShares; +use OC\Repair\NC13\RepairInvalidPaths; use OC\Repair\SqliteAutoincrement; use OC\Repair\RepairMimeTypes; use OC\Repair\RepairInvalidShares; @@ -143,7 +144,8 @@ class Repair implements IOutput{ \OC::$server->query(BundleFetcher::class), \OC::$server->getConfig(), \OC::$server->query(Installer::class) - ) + ), + new RepairInvalidPaths(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()) ]; } @@ -155,7 +157,7 @@ class Repair implements IOutput{ */ public static function getExpensiveRepairSteps() { return [ - new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()), + new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()) ]; } diff --git a/lib/private/Repair/NC13/RepairInvalidPaths.php b/lib/private/Repair/NC13/RepairInvalidPaths.php new file mode 100644 index 00000000000..076fbb735c8 --- /dev/null +++ b/lib/private/Repair/NC13/RepairInvalidPaths.php @@ -0,0 +1,129 @@ +<?php +/** + * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Repair\NC13; + + +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class RepairInvalidPaths implements IRepairStep { + /** @var IDBConnection */ + private $connection; + /** @var IConfig */ + private $config; + + public function __construct(IDBConnection $connection, IConfig $config) { + $this->connection = $connection; + $this->config = $config; + } + + + public function getName() { + return 'Repair invalid paths in file cache'; + } + + private function getInvalidEntries() { + $builder = $this->connection->getQueryBuilder(); + + $computedPath = $builder->func()->concat( + 'p.path', + $builder->func()->concat($builder->createNamedParameter('/'), 'f.name') + ); + + //select f.path, f.parent,p.path from oc_filecache f inner join oc_filecache p on f.parent=p.fileid and p.path!='' where f.path != p.path || '/' || f.name; + $query = $builder->select('f.fileid', 'f.path', 'p.path AS parent_path', 'f.name', 'f.parent', 'f.storage') + ->from('filecache', 'f') + ->innerJoin('f', 'filecache', 'p', $builder->expr()->andX( + $builder->expr()->eq('f.parent', 'p.fileid'), + $builder->expr()->neq('p.name', $builder->createNamedParameter('')) + )) + ->where($builder->expr()->neq('f.path', $computedPath)); + + return $query->execute()->fetchAll(); + } + + private function getId($storage, $path) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->select('fileid') + ->from('filecache') + ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storage))) + ->andWhere($builder->expr()->eq('path', $builder->createNamedParameter($path))); + + return $query->execute()->fetchColumn(); + } + + private function update($fileid, $newPath) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('filecache') + ->set('path', $builder->createNamedParameter($newPath)) + ->set('path_hash', $builder->createNamedParameter(md5($newPath))) + ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileid))); + + $query->execute(); + } + + private function reparent($from, $to) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('filecache') + ->set('parent', $builder->createNamedParameter($to)) + ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($from))); + $query->execute(); + } + + private function delete($fileid) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('filecache') + ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileid))); + $query->execute(); + } + + private function repair() { + $entries = $this->getInvalidEntries(); + foreach ($entries as $entry) { + $calculatedPath = $entry['parent_path'] . '/' . $entry['name']; + if ($newId = $this->getId($entry['storage'], $calculatedPath)) { + // a new entry with the correct path has already been created, reuse that one and delete the incorrect entry + $this->reparent($entry['fileid'], $newId); + $this->delete($entry['fileid']); + } else { + $this->update($entry['fileid'], $calculatedPath); + } + } + return count($entries); + } + + public function run(IOutput $output) { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + // was added to 12.0.0.30 and 13.0.0.1 + if (version_compare($versionFromBeforeUpdate, '12.0.0.30', '<') || version_compare($versionFromBeforeUpdate, '13.0.0.0', '==')) { + $count = $this->repair(); + + $output->info('Repaired ' . $count . ' paths'); + } + } +} diff --git a/lib/public/Encryption/IEncryptionModule.php b/lib/public/Encryption/IEncryptionModule.php index 6be9763c9c8..d96c6c8ba06 100644 --- a/lib/public/Encryption/IEncryptionModule.php +++ b/lib/public/Encryption/IEncryptionModule.php @@ -182,4 +182,14 @@ interface IEncryptionModule { */ public function isReadyForUser($user); + /** + * Does the encryption module needs a detailed list of users with access to the file? + * For example if the encryption module uses per-user encryption keys and needs to know + * the users with access to the file to encrypt/decrypt it. + * + * @since 13.0.0 + * @return bool + */ + public function needDetailedAccessList(); + } diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index a193f9bc8de..76394fcb6c6 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -42,6 +42,8 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\Files\Config\IUserMountCache; +use OCP\Encryption\IEncryptionModule; +use OCP\Encryption\IManager; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; @@ -99,9 +101,14 @@ class UsersController extends Controller { private $keyManager; /** @var IJobList */ private $jobList; + /** @var IUserMountCache */ private $userMountCache; + /** @var IManager */ + private $encryptionManager; + + /** * @param string $appName * @param IRequest $request @@ -124,6 +131,7 @@ class UsersController extends Controller { * @param Manager $keyManager * @param IJobList $jobList * @param IUserMountCache $userMountCache + * @param IManager $encryptionManager */ public function __construct($appName, IRequest $request, @@ -145,7 +153,8 @@ class UsersController extends Controller { ICrypto $crypto, Manager $keyManager, IJobList $jobList, - IUserMountCache $userMountCache) { + IUserMountCache $userMountCache, + IManager $encryptionManager) { parent::__construct($appName, $request); $this->userManager = $userManager; $this->groupManager = $groupManager; @@ -165,6 +174,7 @@ class UsersController extends Controller { $this->keyManager = $keyManager; $this->jobList = $jobList; $this->userMountCache = $userMountCache; + $this->encryptionManager = $encryptionManager; // check for encryption state - TODO see formatUserForIndex $this->isEncryptionAppEnabled = $appManager->isEnabledForUser('encryption'); @@ -200,6 +210,17 @@ class UsersController extends Controller { // user also has recovery mode enabled $restorePossible = true; } + } else { + $modules = $this->encryptionManager->getEncryptionModules(); + $restorePossible = true; + foreach ($modules as $id => $module) { + /* @var IEncryptionModule $instance */ + $instance = call_user_func($module['callback']); + if ($instance->needDetailedAccessList()) { + $restorePossible = false; + break; + } + } } } else { // recovery is possible if encryption is disabled (plain files are diff --git a/settings/l10n/de.js b/settings/l10n/de.js index 4ad00faa5cf..d9edca4a8e9 100644 --- a/settings/l10n/de.js +++ b/settings/l10n/de.js @@ -165,6 +165,7 @@ OC.L10N.register( "A valid group name must be provided" : "Ein gültiger Gruppenname muss angegeben werden", "deleted {groupName}" : "{groupName} gelöscht", "undo" : "rückgängig machen", + "{size} used" : "{size} verwendet", "never" : "niemals", "deleted {userName}" : "{userName} gelöscht", "No user found for <strong>{pattern}</strong>" : "Keine Benutzer für <strong>{pattern}</strong> gefunden", diff --git a/settings/l10n/de.json b/settings/l10n/de.json index 995daf4a7c6..d1f9723cf85 100644 --- a/settings/l10n/de.json +++ b/settings/l10n/de.json @@ -163,6 +163,7 @@ "A valid group name must be provided" : "Ein gültiger Gruppenname muss angegeben werden", "deleted {groupName}" : "{groupName} gelöscht", "undo" : "rückgängig machen", + "{size} used" : "{size} verwendet", "never" : "niemals", "deleted {userName}" : "{userName} gelöscht", "No user found for <strong>{pattern}</strong>" : "Keine Benutzer für <strong>{pattern}</strong> gefunden", diff --git a/settings/l10n/de_DE.js b/settings/l10n/de_DE.js index bc25ee2c106..90d2ae9820e 100644 --- a/settings/l10n/de_DE.js +++ b/settings/l10n/de_DE.js @@ -165,6 +165,7 @@ OC.L10N.register( "A valid group name must be provided" : "Ein gültiger Gruppenname muss angegeben werden", "deleted {groupName}" : "{groupName} gelöscht", "undo" : "rückgängig machen", + "{size} used" : "{size} verwendet", "never" : "niemals", "deleted {userName}" : "{userName} gelöscht", "No user found for <strong>{pattern}</strong>" : "Keine Benutzer für <strong>{pattern}</strong> gefunden", diff --git a/settings/l10n/de_DE.json b/settings/l10n/de_DE.json index 7ef383492cb..cd634d8122f 100644 --- a/settings/l10n/de_DE.json +++ b/settings/l10n/de_DE.json @@ -163,6 +163,7 @@ "A valid group name must be provided" : "Ein gültiger Gruppenname muss angegeben werden", "deleted {groupName}" : "{groupName} gelöscht", "undo" : "rückgängig machen", + "{size} used" : "{size} verwendet", "never" : "niemals", "deleted {userName}" : "{userName} gelöscht", "No user found for <strong>{pattern}</strong>" : "Keine Benutzer für <strong>{pattern}</strong> gefunden", diff --git a/settings/l10n/fr.js b/settings/l10n/fr.js index 135017670e1..49529bec50b 100644 --- a/settings/l10n/fr.js +++ b/settings/l10n/fr.js @@ -165,6 +165,7 @@ OC.L10N.register( "A valid group name must be provided" : "Vous devez indiquer un nom de groupe valide", "deleted {groupName}" : "{groupName} supprimé", "undo" : "annuler", + "{size} used" : "{size} utilisé", "never" : "jamais", "deleted {userName}" : "{userName} supprimé", "No user found for <strong>{pattern}</strong>" : "Aucun utilisateur trouvé pour <strong>{pattern}</strong>", diff --git a/settings/l10n/fr.json b/settings/l10n/fr.json index cef8feadaa8..a1917a75d11 100644 --- a/settings/l10n/fr.json +++ b/settings/l10n/fr.json @@ -163,6 +163,7 @@ "A valid group name must be provided" : "Vous devez indiquer un nom de groupe valide", "deleted {groupName}" : "{groupName} supprimé", "undo" : "annuler", + "{size} used" : "{size} utilisé", "never" : "jamais", "deleted {userName}" : "{userName} supprimé", "No user found for <strong>{pattern}</strong>" : "Aucun utilisateur trouvé pour <strong>{pattern}</strong>", diff --git a/settings/l10n/pt_BR.js b/settings/l10n/pt_BR.js index 74ab13eaf57..031ed372813 100644 --- a/settings/l10n/pt_BR.js +++ b/settings/l10n/pt_BR.js @@ -165,6 +165,7 @@ OC.L10N.register( "A valid group name must be provided" : "Um nome de grupo válido deve ser fornecido", "deleted {groupName}" : "{groupName} excluído", "undo" : "desfazer", + "{size} used" : "{size} usado", "never" : "nunca", "deleted {userName}" : "{userName} excluído", "No user found for <strong>{pattern}</strong>" : "Nenhum usuário encontrado para <strong>{pattern}</strong>", diff --git a/settings/l10n/pt_BR.json b/settings/l10n/pt_BR.json index 5089bc1b077..5154f04b7db 100644 --- a/settings/l10n/pt_BR.json +++ b/settings/l10n/pt_BR.json @@ -163,6 +163,7 @@ "A valid group name must be provided" : "Um nome de grupo válido deve ser fornecido", "deleted {groupName}" : "{groupName} excluído", "undo" : "desfazer", + "{size} used" : "{size} usado", "never" : "nunca", "deleted {userName}" : "{userName} excluído", "No user found for <strong>{pattern}</strong>" : "Nenhum usuário encontrado para <strong>{pattern}</strong>", diff --git a/settings/l10n/ru.js b/settings/l10n/ru.js index 1ffc62da172..bd02a654a97 100644 --- a/settings/l10n/ru.js +++ b/settings/l10n/ru.js @@ -165,6 +165,7 @@ OC.L10N.register( "A valid group name must be provided" : "Введите правильное имя группы", "deleted {groupName}" : "удалена {groupName}", "undo" : "отмена", + "{size} used" : "{size} использовано", "never" : "никогда", "deleted {userName}" : "удалён {userName}", "No user found for <strong>{pattern}</strong>" : "По шаблону <strong>{pattern}</strong> пользователей не найдено", diff --git a/settings/l10n/ru.json b/settings/l10n/ru.json index 67c8d1deffc..8020fa32a50 100644 --- a/settings/l10n/ru.json +++ b/settings/l10n/ru.json @@ -163,6 +163,7 @@ "A valid group name must be provided" : "Введите правильное имя группы", "deleted {groupName}" : "удалена {groupName}", "undo" : "отмена", + "{size} used" : "{size} использовано", "never" : "никогда", "deleted {userName}" : "удалён {userName}", "No user found for <strong>{pattern}</strong>" : "По шаблону <strong>{pattern}</strong> пользователей не найдено", diff --git a/settings/l10n/tr.js b/settings/l10n/tr.js index 81f86909fdc..2663f4c3ac1 100644 --- a/settings/l10n/tr.js +++ b/settings/l10n/tr.js @@ -165,6 +165,7 @@ OC.L10N.register( "A valid group name must be provided" : "Geçerli bir grup adı yazmalısınız", "deleted {groupName}" : "{groupName} silindi", "undo" : "geri al", + "{size} used" : "{size} kullanılmış", "never" : "asla", "deleted {userName}" : "{userName} silindi", "No user found for <strong>{pattern}</strong>" : "<strong>{pattern}</strong> aramasına uyan bir kullanıcı bulunamadı", diff --git a/settings/l10n/tr.json b/settings/l10n/tr.json index a940f18724d..6a2859eced8 100644 --- a/settings/l10n/tr.json +++ b/settings/l10n/tr.json @@ -163,6 +163,7 @@ "A valid group name must be provided" : "Geçerli bir grup adı yazmalısınız", "deleted {groupName}" : "{groupName} silindi", "undo" : "geri al", + "{size} used" : "{size} kullanılmış", "never" : "asla", "deleted {userName}" : "{userName} silindi", "No user found for <strong>{pattern}</strong>" : "<strong>{pattern}</strong> aramasına uyan bir kullanıcı bulunamadı", diff --git a/tests/Settings/Controller/UsersControllerTest.php b/tests/Settings/Controller/UsersControllerTest.php index 0780f5219c0..cd08c834147 100644 --- a/tests/Settings/Controller/UsersControllerTest.php +++ b/tests/Settings/Controller/UsersControllerTest.php @@ -20,6 +20,8 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\Files\Config\IUserMountCache; +use OCP\Encryption\IEncryptionModule; +use OCP\Encryption\IManager; use OCP\IAvatar; use OCP\IAvatarManager; use OCP\IConfig; @@ -82,6 +84,10 @@ class UsersControllerTest extends \Test\TestCase { private $securityManager; /** @var IUserMountCache |\PHPUnit_Framework_MockObject_MockObject */ private $userMountCache; + /** @var IManager | \PHPUnit_Framework_MockObject_MockObject */ + private $encryptionManager; + /** @var IEncryptionModule | \PHPUnit_Framework_MockObject_MockObject */ + private $encryptionModule; protected function setUp() { parent::setUp(); @@ -104,6 +110,7 @@ class UsersControllerTest extends \Test\TestCase { $this->crypto = $this->createMock(ICrypto::class); $this->securityManager = $this->getMockBuilder(\OC\Security\IdentityProof\Manager::class)->disableOriginalConstructor()->getMock(); $this->jobList = $this->createMock(IJobList::class); + $this->encryptionManager = $this->createMock(IManager::class); $this->l = $this->createMock(IL10N::class); $this->l->method('t') ->will($this->returnCallback(function ($text, $parameters = []) { @@ -111,6 +118,10 @@ class UsersControllerTest extends \Test\TestCase { })); $this->userMountCache = $this->createMock(IUserMountCache::class); + $this->encryptionModule = $this->createMock(IEncryptionModule::class); + $this->encryptionManager->expects($this->any())->method('getEncryptionModules') + ->willReturn(['encryptionModule' => ['callback' => function() { return $this->encryptionModule;}]]); + /* * Set default avatar behaviour for whole test suite */ @@ -154,8 +165,8 @@ class UsersControllerTest extends \Test\TestCase { $this->crypto, $this->securityManager, $this->jobList, - $this->userMountCache - + $this->userMountCache, + $this->encryptionManager ); } else { return $this->getMockBuilder(UsersController::class) @@ -182,6 +193,7 @@ class UsersControllerTest extends \Test\TestCase { $this->securityManager, $this->jobList, $this->userMountCache, + $this->encryptionManager ] )->setMethods($mockedMethods)->getMock(); } @@ -1689,9 +1701,17 @@ class UsersControllerTest extends \Test\TestCase { $this->assertEquals($expectedResult, $result); } - public function testRestoreNotPossibleWithoutAdminRestore() { + /** + * @dataProvider dataTestRestoreNotPossibleWithoutAdminRestore + * + * @param bool $masterKeyEnabled + */ + public function testRestoreNotPossibleWithoutAdminRestore($masterKeyEnabled) { list($user, $expectedResult) = $this->mockUser(); + // without the master key enabled we use per-user keys + $this->encryptionModule->expects($this->once())->method('needDetailedAccessList')->willReturn(!$masterKeyEnabled); + $this->appManager ->method('isEnabledForUser') ->with( @@ -1699,7 +1719,8 @@ class UsersControllerTest extends \Test\TestCase { ) ->will($this->returnValue(true)); - $expectedResult['isRestoreDisabled'] = true; + // without the master key enabled we use per-user keys -> restore is disabled + $expectedResult['isRestoreDisabled'] = !$masterKeyEnabled; $subadmin = $this->getMockBuilder('\OC\SubAdmin') ->disableOriginalConstructor() @@ -1718,6 +1739,13 @@ class UsersControllerTest extends \Test\TestCase { $this->assertEquals($expectedResult, $result); } + public function dataTestRestoreNotPossibleWithoutAdminRestore() { + return [ + [true], + [false] + ]; + } + public function testRestoreNotPossibleWithoutUserRestore() { list($user, $expectedResult) = $this->mockUser(); diff --git a/tests/lib/Files/Cache/Wrapper/CacheJailTest.php b/tests/lib/Files/Cache/Wrapper/CacheJailTest.php index e3043c50d57..f26e3a59f1c 100644 --- a/tests/lib/Files/Cache/Wrapper/CacheJailTest.php +++ b/tests/lib/Files/Cache/Wrapper/CacheJailTest.php @@ -8,6 +8,7 @@ namespace Test\Files\Cache\Wrapper; +use OC\Files\Cache\Wrapper\CacheJail; use Test\Files\Cache\CacheTest; /** @@ -80,4 +81,53 @@ class CacheJailTest extends CacheTest { //not supported $this->assertTrue(true); } + + function testMoveFromJail() { + $folderData = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + $this->sourceCache->put('source', $folderData); + $this->sourceCache->put('source/foo', $folderData); + $this->sourceCache->put('source/foo/bar', $folderData); + $this->sourceCache->put('target', $folderData); + + $jail = new CacheJail($this->sourceCache, 'source'); + + $this->sourceCache->moveFromCache($jail, 'foo', 'target/foo'); + + $this->assertTrue($this->sourceCache->inCache('target/foo')); + $this->assertTrue($this->sourceCache->inCache('target/foo/bar')); + } + + function testMoveToJail() { + $folderData = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + $this->sourceCache->put('source', $folderData); + $this->sourceCache->put('source/foo', $folderData); + $this->sourceCache->put('source/foo/bar', $folderData); + $this->sourceCache->put('target', $folderData); + + $jail = new CacheJail($this->sourceCache, 'target'); + + $jail->moveFromCache($this->sourceCache, 'source/foo', 'foo'); + + $this->assertTrue($this->sourceCache->inCache('target/foo')); + $this->assertTrue($this->sourceCache->inCache('target/foo/bar')); + } + + function testMoveBetweenJail() { + $folderData = array('size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'); + + $this->sourceCache->put('source', $folderData); + $this->sourceCache->put('source/foo', $folderData); + $this->sourceCache->put('source/foo/bar', $folderData); + $this->sourceCache->put('target', $folderData); + + $jail = new CacheJail($this->sourceCache, 'target'); + $sourceJail = new CacheJail($this->sourceCache, 'source'); + + $jail->moveFromCache($sourceJail, 'foo', 'foo'); + + $this->assertTrue($this->sourceCache->inCache('target/foo')); + $this->assertTrue($this->sourceCache->inCache('target/foo/bar')); + } } diff --git a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php index d310f110b94..a66ff14a778 100644 --- a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php +++ b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php @@ -212,7 +212,7 @@ class EncryptionTest extends Storage { protected function buildMockModule() { $this->encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule') ->disableOriginalConstructor() - ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser']) + ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser', 'needDetailedAccessList']) ->getMock(); $this->encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE'); @@ -225,6 +225,7 @@ class EncryptionTest extends Storage { $this->encryptionModule->expects($this->any())->method('shouldEncrypt')->willReturn(true); $this->encryptionModule->expects($this->any())->method('getUnencryptedBlockSize')->willReturn(8192); $this->encryptionModule->expects($this->any())->method('isReadable')->willReturn(true); + $this->encryptionModule->expects($this->any())->method('needDetailedAccessList')->willReturn(false); return $this->encryptionModule; } diff --git a/tests/lib/Files/Stream/EncryptionTest.php b/tests/lib/Files/Stream/EncryptionTest.php index e072dd6718d..1dc9dca0aad 100644 --- a/tests/lib/Files/Stream/EncryptionTest.php +++ b/tests/lib/Files/Stream/EncryptionTest.php @@ -58,7 +58,8 @@ class EncryptionTest extends \Test\TestCase { /** * @dataProvider dataProviderStreamOpen() */ - public function testStreamOpen($mode, + public function testStreamOpen($isMasterKeyUsed, + $mode, $fullPath, $fileExists, $expectedSharePath, @@ -69,6 +70,7 @@ class EncryptionTest extends \Test\TestCase { // build mocks $encryptionModuleMock = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule') ->disableOriginalConstructor()->getMock(); + $encryptionModuleMock->expects($this->any())->method('needDetailedAccessList')->willReturn(!$isMasterKeyUsed); $encryptionModuleMock->expects($this->once()) ->method('getUnencryptedBlockSize')->willReturn(99); $encryptionModuleMock->expects($this->once()) @@ -80,12 +82,15 @@ class EncryptionTest extends \Test\TestCase { $fileMock = $this->getMockBuilder('\OC\Encryption\File') ->disableOriginalConstructor()->getMock(); - $fileMock->expects($this->once())->method('getAccessList') - ->will($this->returnCallback(function($sharePath) use ($expectedSharePath) { - $this->assertSame($expectedSharePath, $sharePath); - return array(); - })); - + if ($isMasterKeyUsed) { + $fileMock->expects($this->never())->method('getAccessList'); + } else { + $fileMock->expects($this->once())->method('getAccessList') + ->will($this->returnCallback(function ($sharePath) use ($expectedSharePath) { + $this->assertSame($expectedSharePath, $sharePath); + return array(); + })); + } $utilMock = $this->getMockBuilder('\OC\Encryption\Util') ->disableOriginalConstructor()->getMock(); $utilMock->expects($this->any()) @@ -152,11 +157,14 @@ class EncryptionTest extends \Test\TestCase { } public function dataProviderStreamOpen() { - return array( - array('r', '/foo/bar/test.txt', true, '/foo/bar/test.txt', null, null, true), - array('r', '/foo/bar/test.txt', false, '/foo/bar', null, null, true), - array('w', '/foo/bar/test.txt', true, '/foo/bar/test.txt', 8192, 0, false), - ); + return [ + [false, 'r', '/foo/bar/test.txt', true, '/foo/bar/test.txt', null, null, true], + [false, 'r', '/foo/bar/test.txt', false, '/foo/bar', null, null, true], + [false, 'w', '/foo/bar/test.txt', true, '/foo/bar/test.txt', 8192, 0, false], + [true, 'r', '/foo/bar/test.txt', true, '/foo/bar/test.txt', null, null, true], + [true, 'r', '/foo/bar/test.txt', false, '/foo/bar', null, null, true], + [true, 'w', '/foo/bar/test.txt', true, '/foo/bar/test.txt', 8192, 0, false], + ]; } public function testWriteRead() { @@ -193,7 +201,7 @@ class EncryptionTest extends \Test\TestCase { $stream = $this->getStream($fileName, 'r', 6); $this->assertEquals('barbar', fread($stream, 100)); fclose($stream); - + unlink($fileName); } @@ -311,7 +319,7 @@ class EncryptionTest extends \Test\TestCase { protected function buildMockModule() { $encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule') ->disableOriginalConstructor() - ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser']) + ->setMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser', 'needDetailedAccessList']) ->getMock(); $encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE'); @@ -319,6 +327,7 @@ class EncryptionTest extends \Test\TestCase { $encryptionModule->expects($this->any())->method('begin')->willReturn([]); $encryptionModule->expects($this->any())->method('end')->willReturn(''); $encryptionModule->expects($this->any())->method('isReadable')->willReturn(true); + $encryptionModule->expects($this->any())->method('needDetailedAccessList')->willReturn(false); $encryptionModule->expects($this->any())->method('encrypt')->willReturnCallback(function($data) { // simulate different block size by adding some padding to the data if (isset($data[6125])) { diff --git a/tests/lib/NavigationManagerTest.php b/tests/lib/NavigationManagerTest.php index 0871a9a0910..de432e1eaf2 100644 --- a/tests/lib/NavigationManagerTest.php +++ b/tests/lib/NavigationManagerTest.php @@ -13,7 +13,9 @@ namespace Test; use OC\App\AppManager; +use OC\Group\Manager; use OC\NavigationManager; +use OC\SubAdmin; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; @@ -46,7 +48,7 @@ class NavigationManagerTest extends TestCase { $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->l10nFac = $this->createMock(IFactory::class); $this->userSession = $this->createMock(IUserSession::class); - $this->groupManager = $this->createMock(IGroupManager::class); + $this->groupManager = $this->createMock(Manager::class); $this->config = $this->createMock(IConfig::class); $this->navigationManager = new NavigationManager( $this->appManager, @@ -207,19 +209,26 @@ class NavigationManagerTest extends TestCase { return vsprintf($text, $parameters); }); - $this->appManager->expects($this->once())->method('getInstalledApps')->willReturn(['test']); $this->appManager->expects($this->once())->method('getAppInfo')->with('test')->willReturn($navigation); - $this->l10nFac->expects($this->exactly(count($expected) + 1))->method('get')->willReturn($l); + $this->l10nFac->expects($this->any())->method('get')->willReturn($l); $this->urlGenerator->expects($this->any())->method('imagePath')->willReturnCallback(function($appName, $file) { return "/apps/$appName/img/$file"; }); - $this->urlGenerator->expects($this->exactly(count($expected)))->method('linkToRoute')->willReturnCallback(function() { + $this->urlGenerator->expects($this->any())->method('linkToRoute')->willReturnCallback(function() { return "/apps/test/"; }); $user = $this->createMock(IUser::class); $user->expects($this->any())->method('getUID')->willReturn('user001'); $this->userSession->expects($this->any())->method('getUser')->willReturn($user); + $this->userSession->expects($this->any())->method('isLoggedIn')->willReturn(true); + $this->appManager->expects($this->once()) + ->method('getEnabledAppsForUser') + ->with($user) + ->willReturn(['test']); $this->groupManager->expects($this->any())->method('isAdmin')->willReturn($isAdmin); + $subadmin = $this->createMock(SubAdmin::class); + $subadmin->expects($this->any())->method('isSubAdmin')->with($user)->willReturn(false); + $this->groupManager->expects($this->any())->method('getSubAdmin')->willReturn($subadmin); $this->navigationManager->clear(); $entries = $this->navigationManager->getAll('all'); @@ -227,8 +236,39 @@ class NavigationManagerTest extends TestCase { } public function providesNavigationConfig() { + $apps = [ + [ + 'id' => 'core_apps', + 'order' => 3, + 'href' => '/apps/test/', + 'icon' => '/apps/settings/img/apps.svg', + 'name' => 'Apps', + 'active' => false, + 'type' => 'settings', + ] + ]; + $defaults = [ + [ + 'id' => 'settings', + 'order' => 1, + 'href' => '/apps/test/', + 'icon' => '/apps/settings/img/admin.svg', + 'name' => 'Settings', + 'active' => false, + 'type' => 'settings', + ], + [ + 'id' => 'logout', + 'order' => 99999, + 'href' => null, + 'icon' => '/apps/core/img/actions/logout.svg', + 'name' => 'Log out', + 'active' => false, + 'type' => 'settings', + ], + ]; return [ - 'minimalistic' => [[[ + 'minimalistic' => [array_merge($defaults, [[ 'id' => 'test', 'order' => 100, 'href' => '/apps/test/', @@ -236,8 +276,8 @@ class NavigationManagerTest extends TestCase { 'name' => 'Test', 'active' => false, 'type' => 'link', - ]], ['navigations' => [['route' => 'test.page.index', 'name' => 'Test']]]], - 'minimalistic-settings' => [[[ + ]]), ['navigations' => [['route' => 'test.page.index', 'name' => 'Test']]]], + 'minimalistic-settings' => [array_merge($defaults, [[ 'id' => 'test', 'order' => 100, 'href' => '/apps/test/', @@ -245,8 +285,8 @@ class NavigationManagerTest extends TestCase { 'name' => 'Test', 'active' => false, 'type' => 'settings', - ]], ['navigations' => [['route' => 'test.page.index', 'name' => 'Test', 'type' => 'settings']]]], - 'no admin' => [[[ + ]]), ['navigations' => [['route' => 'test.page.index', 'name' => 'Test', 'type' => 'settings']]]], + 'admin' => [array_merge($apps, $defaults, [[ 'id' => 'test', 'order' => 100, 'href' => '/apps/test/', @@ -254,9 +294,9 @@ class NavigationManagerTest extends TestCase { 'name' => 'Test', 'active' => false, 'type' => 'link', - ]], ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test']]], true], - 'no name' => [[], ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index']]], true], - 'admin' => [[], ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test']]]] + ]]), ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test']]], true], + 'no name' => [array_merge($apps, $defaults), ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index']]], true], + 'no admin' => [$defaults, ['navigations' => [['@attributes' => ['role' => 'admin'], 'route' => 'test.page.index', 'name' => 'Test']]]] ]; } } diff --git a/tests/lib/Repair/RepairInvalidPathsTest.php b/tests/lib/Repair/RepairInvalidPathsTest.php new file mode 100644 index 00000000000..b18758585c1 --- /dev/null +++ b/tests/lib/Repair/RepairInvalidPathsTest.php @@ -0,0 +1,117 @@ +<?php +/** + * @copyright Copyright (c) 2017 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace Test\Repair; + +use OC\Files\Cache\Cache; +use OC\Files\Storage\Temporary; +use OC\Repair\NC13\RepairInvalidPaths; +use OCP\IConfig; +use OCP\Migration\IOutput; +use Test\TestCase; + +/** + * @group DB + */ +class RepairInvalidPathsTest extends TestCase { + /** @var Temporary */ + private $storage; + /** @var Cache */ + private $cache; + /** @var RepairInvalidPaths */ + private $repair; + + protected function setUp() { + parent::setUp(); + + $this->storage = new Temporary(); + $this->cache = $this->storage->getCache(); + $config = $this->createMock(IConfig::class); + $config->expects($this->any()) + ->method('getSystemValue') + ->with('version', '0.0.0') + ->willReturn('12.0.0.0'); + $this->repair = new RepairInvalidPaths(\OC::$server->getDatabaseConnection(), $config); + } + + protected function tearDown() { + $this->cache->clear(); + + return parent::tearDown(); + } + + public function testRepairNonDuplicate() { + $this->storage->mkdir('foo/bar/asd'); + $this->storage->mkdir('foo2'); + $this->storage->getScanner()->scan(''); + + $folderId = $this->cache->getId('foo/bar'); + $newParentFolderId = $this->cache->getId('foo2'); + // failed rename, moved entry is updated but not it's children + $this->cache->update($folderId, ['path' => 'foo2/bar', 'parent' => $newParentFolderId]); + + $this->assertTrue($this->cache->inCache('foo2/bar')); + $this->assertTrue($this->cache->inCache('foo/bar/asd')); + $this->assertFalse($this->cache->inCache('foo2/bar/asd')); + + $this->assertEquals($folderId, $this->cache->get('foo/bar/asd')['parent']); + + $this->repair->run($this->createMock(IOutput::class)); + + $this->assertTrue($this->cache->inCache('foo2/bar')); + $this->assertTrue($this->cache->inCache('foo2/bar/asd')); + $this->assertFalse($this->cache->inCache('foo/bar/asd')); + + $this->assertEquals($folderId, $this->cache->get('foo2/bar/asd')['parent']); + $this->assertEquals($folderId, $this->cache->getId('foo2/bar')); + } + + public function testRepairDuplicate() { + $this->storage->mkdir('foo/bar/asd'); + $this->storage->mkdir('foo2'); + $this->storage->getScanner()->scan(''); + + $folderId = $this->cache->getId('foo/bar'); + $newParentFolderId = $this->cache->getId('foo2'); + // failed rename, moved entry is updated but not it's children + $this->cache->update($folderId, ['path' => 'foo2/bar', 'parent' => $newParentFolderId]); + $this->storage->rename('foo/bar', 'foo2/bar'); + $this->storage->mkdir('foo2/bar/asd/foo'); + + // usage causes the renamed subfolder to be scanned + $this->storage->getScanner()->scan('foo2/bar/asd'); + + $this->assertTrue($this->cache->inCache('foo2/bar')); + $this->assertTrue($this->cache->inCache('foo/bar/asd')); + $this->assertTrue($this->cache->inCache('foo2/bar/asd')); + + $this->assertEquals($folderId, $this->cache->get('foo/bar/asd')['parent']); + + $this->repair->run($this->createMock(IOutput::class)); + + $this->assertTrue($this->cache->inCache('foo2/bar')); + $this->assertTrue($this->cache->inCache('foo2/bar/asd')); + $this->assertFalse($this->cache->inCache('foo/bar/asd')); + + $this->assertEquals($this->cache->getId('foo2/bar'), $this->cache->get('foo2/bar/asd')['parent']); + $this->assertEquals($this->cache->getId('foo2/bar/asd'), $this->cache->get('foo2/bar/asd/foo')['parent']); + } +} diff --git a/tests/lib/Traits/EncryptionTrait.php b/tests/lib/Traits/EncryptionTrait.php index 5e2ca4e561f..8a06d37fa7f 100644 --- a/tests/lib/Traits/EncryptionTrait.php +++ b/tests/lib/Traits/EncryptionTrait.php @@ -64,6 +64,7 @@ trait EncryptionTrait { /** @var Setup $userSetup */ $userSetup = $container->query('UserSetup'); $userSetup->setupUser($name, $password); + $this->encryptionApp->setUp(); $keyManager->init($name, $password); } @@ -99,6 +100,7 @@ trait EncryptionTrait { if ($this->config) { $this->config->setAppValue('core', 'encryption_enabled', $this->encryptionWasEnabled); $this->config->setAppValue('core', 'default_encryption_module', $this->originalEncryptionModule); + $this->config->deleteAppValue('encryption', 'useMasterKey'); } } } |