From 81761b87e4d29989329b398d4f797f0ccb47b144 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 10 Jun 2016 11:03:19 +0200 Subject: [PATCH 01/23] DAV now returns file name with Content-Disposition header --- apps/dav/lib/connector/sabre/filesplugin.php | 29 ++++++++++++++++--- .../dav/lib/connector/sabre/serverfactory.php | 6 +++- apps/dav/lib/server.php | 6 +++- .../unit/connector/sabre/filesplugin.php | 13 +++++++-- .../connector/sabre/filesreportplugin.php | 6 +++- .../features/webdav-related.feature | 4 +-- 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/apps/dav/lib/connector/sabre/filesplugin.php b/apps/dav/lib/connector/sabre/filesplugin.php index bf9bee0257..857c98247b 100644 --- a/apps/dav/lib/connector/sabre/filesplugin.php +++ b/apps/dav/lib/connector/sabre/filesplugin.php @@ -76,16 +76,26 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin { */ private $fileView; + /** + * @var IRequest + */ + private $request; + /** * @param \Sabre\DAV\Tree $tree * @param \OC\Files\View $view + * @param \OCP\IRequest $request * @param bool $isPublic */ - public function __construct(\Sabre\DAV\Tree $tree, - \OC\Files\View $view, - $isPublic = false) { + public function __construct( + \Sabre\DAV\Tree $tree, + \OC\Files\View $view, + \OCP\IRequest $request, + $isPublic = false + ) { $this->tree = $tree; $this->fileView = $view; + $this->request = $request; $this->isPublic = $isPublic; } @@ -193,7 +203,18 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin { if (!($node instanceof IFile)) return; // adds a 'Content-Disposition: attachment' header - $response->addHeader('Content-Disposition', 'attachment'); + $filename = $node->getName(); + if ($this->request->isUserAgent( + [ + \OC\AppFramework\Http\Request::USER_AGENT_IE, + \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, + \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, + ])) { + $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"'); + } else { + $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename) + . '; filename="' . rawurlencode($filename) . '"'); + } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { //Add OC-Checksum header diff --git a/apps/dav/lib/connector/sabre/serverfactory.php b/apps/dav/lib/connector/sabre/serverfactory.php index c0b45c36a0..8462f62455 100644 --- a/apps/dav/lib/connector/sabre/serverfactory.php +++ b/apps/dav/lib/connector/sabre/serverfactory.php @@ -137,7 +137,11 @@ class ServerFactory { } $objectTree->init($root, $view, $this->mountManager); - $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin($objectTree, $view)); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin( + $objectTree, + $view, + $this->request + )); $server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view)); if($this->userSession->isLoggedIn()) { diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index 4711a80800..791bd1ba7e 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -125,7 +125,11 @@ class Server { $user = \OC::$server->getUserSession()->getUser(); if (!is_null($user)) { $view = \OC\Files\Filesystem::getView(); - $this->server->addPlugin(new FilesPlugin($this->server->tree, $view)); + $this->server->addPlugin(new FilesPlugin( + $this->server->tree, + $view, + $this->request + )); $this->server->addPlugin( new \Sabre\DAV\PropertyStorage\Plugin( diff --git a/apps/dav/tests/unit/connector/sabre/filesplugin.php b/apps/dav/tests/unit/connector/sabre/filesplugin.php index 0a790ec6fc..ecad56d004 100644 --- a/apps/dav/tests/unit/connector/sabre/filesplugin.php +++ b/apps/dav/tests/unit/connector/sabre/filesplugin.php @@ -72,8 +72,13 @@ class FilesPlugin extends \Test\TestCase { $this->view = $this->getMockBuilder('\OC\Files\View') ->disableOriginalConstructor() ->getMock(); + $request = $this->getMock('\OCP\IRequest'); - $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view); + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $request + ); $this->plugin->initialize($this->server); } @@ -237,7 +242,11 @@ class FilesPlugin extends \Test\TestCase { } public function testGetPublicPermissions() { - $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view, true); + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $this->getMock('\OCP\IRequest'), + true); $this->plugin->initialize($this->server); $propFind = new \Sabre\DAV\PropFind( diff --git a/apps/dav/tests/unit/connector/sabre/filesreportplugin.php b/apps/dav/tests/unit/connector/sabre/filesreportplugin.php index 87973ef007..c17cc0f30a 100644 --- a/apps/dav/tests/unit/connector/sabre/filesreportplugin.php +++ b/apps/dav/tests/unit/connector/sabre/filesreportplugin.php @@ -336,7 +336,11 @@ class FilesReportPlugin extends \Test\TestCase { ->method('getSize') ->will($this->returnValue(1024)); - $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view)); + $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $this->getMock('\OCP\IRequest') + )); $this->plugin->initialize($this->server); $responses = $this->plugin->prepareResponses($requestedProps, [$node1, $node2]); diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index ee841f9eb5..45a3649925 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -73,7 +73,7 @@ Feature: webdav-related And As an "admin" When Downloading file "/welcome.txt" Then The following headers should be set - |Content-Disposition|attachment| + |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"| |Content-Security-Policy|default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; frame-src *; img-src * data: blob:; font-src 'self' data:; media-src *; connect-src *| |X-Content-Type-Options |nosniff| |X-Download-Options|noopen| @@ -88,7 +88,7 @@ Feature: webdav-related And As an "admin" When Downloading file "/welcome.txt" Then The following headers should be set - |Content-Disposition|attachment| + |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"| |Content-Security-Policy|default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; frame-src *; img-src * data: blob:; font-src 'self' data:; media-src *; connect-src *| |X-Content-Type-Options |nosniff| |X-Download-Options|noopen| From 9e3cf794ea9af6bd0138e48ca9e1cb68a9157019 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 13 Jun 2016 17:47:57 +0200 Subject: [PATCH 02/23] Use capped cache for encryption's user access list --- lib/private/encryption/file.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/private/encryption/file.php b/lib/private/encryption/file.php index ec55c2cea0..bd045c2029 100644 --- a/lib/private/encryption/file.php +++ b/lib/private/encryption/file.php @@ -22,6 +22,8 @@ namespace OC\Encryption; +use OC\Cache\CappedMemoryCache; + class File implements \OCP\Encryption\IFile { /** @var Util */ @@ -36,6 +38,7 @@ class File implements \OCP\Encryption\IFile { public function __construct(Util $util) { $this->util = $util; + $this->cache = new CappedMemoryCache(); } From 3e846a4821de32396bc33120633b85abc47e2373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 14 Jun 2016 12:11:35 +0200 Subject: [PATCH 03/23] Use an explicit version of sabre/dav to allow caching on the jenkins slaves - fixes #25087 (#25089) --- build/integration/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/integration/composer.json b/build/integration/composer.json index a9516391a4..29f73f2064 100644 --- a/build/integration/composer.json +++ b/build/integration/composer.json @@ -4,6 +4,6 @@ "behat/behat": "^3.0", "guzzlehttp/guzzle": "~5.0", "jarnaiz/behat-junit-formatter": "^1.3", - "sabre/dav": "3.0.x-dev" + "sabre/dav": "3.0.9" } } From a0215b191d91501835e8387fb791143a596864bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 7 Jun 2016 11:40:04 +0200 Subject: [PATCH 04/23] decrease initial users to load to 50 Prevents timeouts on the initial loading of users. proper fix will be in https://github.com/owncloud/core/pull/10994 Workaround for https://github.com/owncloud/core/issues/24734 --- settings/js/users/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/js/users/users.js b/settings/js/users/users.js index 02d3a25be7..eb041054cd 100644 --- a/settings/js/users/users.js +++ b/settings/js/users/users.js @@ -14,7 +14,7 @@ var UserList = { availableGroups: [], offset: 0, usersToLoad: 10, //So many users will be loaded when user scrolls down - initialUsersToLoad: 250, //initial number of users to load + initialUsersToLoad: 50, //initial number of users to load currentGid: '', filter: '', From ea330dfa6a249985422728a66ccc2a7722a2223f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 10 Jun 2016 11:16:32 +0200 Subject: [PATCH 05/23] Allow empty host when installing on oracle via CLI (#25034) --- core/command/maintenance/install.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/command/maintenance/install.php b/core/command/maintenance/install.php index b1b63b9b3b..12a61d6341 100644 --- a/core/command/maintenance/install.php +++ b/core/command/maintenance/install.php @@ -106,7 +106,12 @@ class Install extends Command { $dbUser = $input->getOption('database-user'); $dbPass = $input->getOption('database-pass'); $dbName = $input->getOption('database-name'); - $dbHost = $input->getOption('database-host'); + if ($db === 'oci') { + // an empty hostname needs to be read from the raw parameters + $dbHost = $input->getParameterOption('--database-host', ''); + } else { + $dbHost = $input->getOption('database-host'); + } $dbTablePrefix = 'oc_'; if ($input->hasParameterOption('--database-table-prefix')) { $dbTablePrefix = (string) $input->getOption('database-table-prefix'); From a9c98daffb0e6633b3050c1944e6d6ca652abd9f Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 15 Jun 2016 13:02:39 +0200 Subject: [PATCH 06/23] Capped cache for cache info in UserMountCache --- lib/private/files/config/usermountcache.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/private/files/config/usermountcache.php b/lib/private/files/config/usermountcache.php index 78b1997278..76281ef62e 100644 --- a/lib/private/files/config/usermountcache.php +++ b/lib/private/files/config/usermountcache.php @@ -35,6 +35,7 @@ use OCP\IDBConnection; use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; +use OC\Cache\CappedMemoryCache; /** * Cache mounts points per user in the cache so we can easilly look them up @@ -50,15 +51,23 @@ class UserMountCache implements IUserMountCache { */ private $userManager; - /** @var ICachedMountInfo[][] [$userId => [$cachedMountInfo, ....], ...] */ - private $mountsForUsers = []; + /** + * Cached mount info. + * Map of $userId to ICachedMountInfo. + * + * @var ICache + **/ + private $mountsForUsers; /** * @var ILogger */ private $logger; - private $cacheInfoCache = []; + /** + * @var ICache + */ + private $cacheInfoCache; /** * UserMountCache constructor. @@ -71,6 +80,8 @@ class UserMountCache implements IUserMountCache { $this->connection = $connection; $this->userManager = $userManager; $this->logger = $logger; + $this->cacheInfoCache = new CappedMemoryCache(); + $this->mountsForUsers = new CappedMemoryCache(); } public function registerMounts(IUser $user, array $mounts) { From 2656f68d608fafa422be144c0aeccaff9af25a7e Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 15 Jun 2016 21:37:01 +0200 Subject: [PATCH 07/23] load authentication apps first --- ocs/v1.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ocs/v1.php b/ocs/v1.php index 32fcfdd46d..74b6ac1389 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -43,6 +43,8 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Exception\MethodNotAllowedException; try { + OC_App::loadApps(['session']); + OC_App::loadApps(['authentication']); // load all apps to get all api routes properly setup OC_App::loadApps(); From 416d4c5141bc83970b94926f85e2423a60ba22a2 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Thu, 16 Jun 2016 11:43:57 +0200 Subject: [PATCH 08/23] fix grouped input fields, make sure they take precedence --- core/css/styles.css | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/css/styles.css b/core/css/styles.css index a429d55560..46607388a2 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -365,26 +365,26 @@ body { } #body-login .grouptop input, .grouptop input { - margin-bottom: 0; - border-bottom: 0; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + margin-bottom: 0 !important; + border-bottom: 0 !important; + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; } #body-login .groupmiddle input, .groupmiddle input { - margin-top: 0; - margin-bottom: 0; - border-top: 0; - border-bottom: 0; - border-radius: 0; + margin-top: 0 !important; + margin-bottom: 0 !important; + border-top: 0 !important; + border-bottom: 0 !important; + border-radius: 0 !important; box-shadow: 0 1px 0 rgba(0,0,0,.1) inset !important; } #body-login .groupbottom input, .groupbottom input { - margin-top: 0; - border-top: 0; - border-top-right-radius: 0; - border-top-left-radius: 0; + margin-top: 0 !important; + border-top: 0 !important; + border-top-right-radius: 0 !important; + border-top-left-radius: 0 !important; box-shadow: 0 1px 0 rgba(0,0,0,.1) inset !important; } #body-login .groupbottom input[type=submit] { From 5e17e992b0f2848969ee53369c6bcd7cf52d4dfa Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 16 Jun 2016 16:05:14 +0200 Subject: [PATCH 09/23] Convert Dropbox Forbidden exception to StorageNotAvailableException --- apps/files_external/lib/dropbox.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php index 8381ccbae5..bfddc98f5a 100644 --- a/apps/files_external/lib/dropbox.php +++ b/apps/files_external/lib/dropbox.php @@ -32,6 +32,7 @@ namespace OC\Files\Storage; use GuzzleHttp\Exception\RequestException; use Icewind\Streams\IteratorDirectory; use Icewind\Streams\RetryWrapper; +use OCP\Files\StorageNotAvailableException; require_once __DIR__ . '/../3rdparty/Dropbox/autoload.php'; @@ -94,6 +95,8 @@ class Dropbox extends \OC\Files\Storage\Common { if ($list) { try { $response = $this->dropbox->getMetaData($path); + } catch (\Dropbox_Exception_Forbidden $e) { + throw new StorageNotAvailableException('Dropbox API rate limit exceeded', StorageNotAvailableException::STATUS_ERROR, $e); } catch (\Exception $exception) { \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); return false; @@ -127,6 +130,8 @@ class Dropbox extends \OC\Files\Storage\Common { return $response; } return null; + } catch (\Dropbox_Exception_Forbidden $e) { + throw new StorageNotAvailableException('Dropbox API rate limit exceeded', StorageNotAvailableException::STATUS_ERROR, $e); } catch (\Exception $exception) { if ($exception instanceof \Dropbox_Exception_NotFound) { // don't log, might be a file_exist check From 3ac02c9032ee1b28f55c937e61c7f920e682649b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 16 Jun 2016 17:17:02 +0200 Subject: [PATCH 10/23] =?UTF-8?q?=C2=96emit=20correct=20signal=20when=20di?= =?UTF-8?q?sabling=20an=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/private/app/appmanager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/app/appmanager.php b/lib/private/app/appmanager.php index fba5bb19f6..608c69dd54 100644 --- a/lib/private/app/appmanager.php +++ b/lib/private/app/appmanager.php @@ -256,7 +256,7 @@ class AppManager implements IAppManager { } unset($this->installedAppsCache[$appId]); $this->appConfig->setValue($appId, 'enabled', 'no'); - $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent( + $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent( ManagerEvent::EVENT_APP_DISABLE, $appId )); $this->clearAppsCache(); From 2ce078e7c3ffe3e3901f8ae7f44c8741ef5fa370 Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Thu, 16 Jun 2016 18:23:42 +0200 Subject: [PATCH 11/23] ownCloud 9.0.3 RC1 --- version.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.php b/version.php index c90e84a79c..5e9923f957 100644 --- a/version.php +++ b/version.php @@ -26,10 +26,10 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(9, 0, 2, 2); +$OC_Version = array(9, 0, 3, 1); // The human readable string -$OC_VersionString = '9.0.2'; +$OC_VersionString = '9.0.3 RC1'; $OC_VersionCanBeUpgradedFrom = array(8, 2); From b5d3e877f1554a261da9d5a1fe0381f30607815b Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 17 Jun 2016 10:06:08 +0200 Subject: [PATCH 12/23] Remove shares of the root folder (#25138) --- lib/private/repair.php | 2 + lib/private/repair/removerootshares.php | 145 ++++++++++++++++ tests/lib/repair/removerootsharestest.php | 194 ++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 lib/private/repair/removerootshares.php create mode 100644 tests/lib/repair/removerootsharestest.php diff --git a/lib/private/repair.php b/lib/private/repair.php index 098d4e7eb9..b2a1e5a070 100644 --- a/lib/private/repair.php +++ b/lib/private/repair.php @@ -48,6 +48,7 @@ use OC\Repair\RepairMimeTypes; use OC\Repair\SearchLuceneTables; use OC\Repair\UpdateOutdatedOcsIds; use OC\Repair\RepairInvalidShares; +use OC\Repair\RemoveRootShares; class Repair extends BasicEmitter { /** @@ -118,6 +119,7 @@ class Repair extends BasicEmitter { new UpdateOutdatedOcsIds(\OC::$server->getConfig()), new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new AvatarPermissions(\OC::$server->getDatabaseConnection()), + new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getRootFolder()), ]; } diff --git a/lib/private/repair/removerootshares.php b/lib/private/repair/removerootshares.php new file mode 100644 index 0000000000..bf7061c083 --- /dev/null +++ b/lib/private/repair/removerootshares.php @@ -0,0 +1,145 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OC\Repair; + +use OCP\Files\IRootFolder; +use OCP\IDBConnection; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Migration\IOutput; + +/** + * Class RemoveRootShares + * + * @package OC\Repair + */ +class RemoveRootShares implements \OC\RepairStep { + + /** @var IDBConnection */ + protected $connection; + + /** @var IUserManager */ + protected $userManager; + + /** @var IRootFolder */ + protected $rootFolder; + + /** + * RemoveRootShares constructor. + * + * @param IDBConnection $connection + * @param IUserManager $userManager + * @param IRootFolder $rootFolder + */ + public function __construct(IDBConnection $connection, + IUserManager $userManager, + IRootFolder $rootFolder) { + $this->connection = $connection; + $this->userManager = $userManager; + $this->rootFolder = $rootFolder; + } + + /** + * @return string + */ + public function getName() { + return 'Remove shares of a users root folder'; + } + + public function run() { + if ($this->rootSharesExist()) { + $this->removeRootShares(); + } + } + + private function removeRootShares() { + $function = function(IUser $user) { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $fileId = $userFolder->getId(); + + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('file_source', $qb->createNamedParameter($fileId))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->expr()->literal('file')), + $qb->expr()->eq('item_type', $qb->expr()->literal('folder')) + )); + + $qb->execute(); + }; + + $userCount = $this->countUsers(); + + $this->userManager->callForAllUsers($function); + } + + /** + * Count all the users + * + * @return int + */ + private function countUsers() { + $allCount = $this->userManager->countUsers(); + + $totalCount = 0; + foreach ($allCount as $backend => $count) { + $totalCount += $count; + } + + return $totalCount; + } + + /** + * Verify if this repair steps is required + * It *should* not be necessary in most cases and it can be very + * costly. + * + * @return bool + */ + private function rootSharesExist() { + $qb = $this->connection->getQueryBuilder(); + $qb2 = $this->connection->getQueryBuilder(); + + $qb->select('fileid') + ->from('filecache') + ->where($qb->expr()->eq('path', $qb->expr()->literal('files'))); + + $qb2->select('id') + ->from('share') + ->where($qb2->expr()->in('file_source', $qb2->createFunction($qb->getSQL()))) + ->andWhere($qb2->expr()->orX( + $qb2->expr()->eq('item_type', $qb->expr()->literal('file')), + $qb2->expr()->eq('item_type', $qb->expr()->literal('folder')) + )) + ->setMaxResults(1); + + $cursor = $qb2->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + return false; + } + + return true; + } +} + diff --git a/tests/lib/repair/removerootsharestest.php b/tests/lib/repair/removerootsharestest.php new file mode 100644 index 0000000000..bf255fc7e9 --- /dev/null +++ b/tests/lib/repair/removerootsharestest.php @@ -0,0 +1,194 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace Test\Repair; + +use OC\Repair\RemoveRootShares; +use OCP\Files\IRootFolder; +use OCP\IDBConnection; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use Test\Traits\UserTrait; + +/** + * Class RemoveOldSharesTest + * + * @package Test\Repair + * @group DB + */ +class RemoveRootSharesTest extends \Test\TestCase { + use UserTrait; + + /** @var RemoveRootShares */ + protected $repair; + + /** @var IDBConnection */ + protected $connection; + + /** @var IOutput */ + private $outputMock; + + /** @var IUserManager */ + private $userManager; + + /** @var IRootFolder */ + private $rootFolder; + + protected function setUp() { + parent::setUp(); + + $this->outputMock = $this->getMockBuilder('\OCP\Migration\IOutput') + ->disableOriginalConstructor() + ->getMock(); + + $this->userManager = \OC::$server->getUserManager(); + $this->rootFolder = \OC::$server->getRootFolder(); + + $this->connection = \OC::$server->getDatabaseConnection(); + $this->repair = new RemoveRootShares($this->connection, $this->userManager, $this->rootFolder); + } + + protected function tearDown() { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share'); + $qb->execute(); + + return parent::tearDown(); + } + + public function testRootSharesExist() { + //Add test user + $user = $this->userManager->createUser('test', 'test'); + $userFolder = $this->rootFolder->getUserFolder('test'); + $fileId = $userFolder->getId(); + + //Now insert cyclic share + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('file'), + 'item_source' => $qb->createNamedParameter($fileId), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter($fileId), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $res = $this->invokePrivate($this->repair, 'rootSharesExist', []); + $this->assertTrue($res); + + $user->delete(); + } + + public function testRootSharesDontExist() { + //Add test user + $user = $this->userManager->createUser('test', 'test'); + $userFolder = $this->rootFolder->getUserFolder('test'); + $fileId = $userFolder->getId(); + + //Now insert cyclic share + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('file'), + 'item_source' => $qb->createNamedParameter($fileId+1), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter($fileId+1), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $res = $this->invokePrivate($this->repair, 'rootSharesExist', []); + $this->assertFalse($res); + + $user->delete(); + } + + public function testRun() { + //Add test user + $user1 = $this->userManager->createUser('test1', 'test1'); + $userFolder = $this->rootFolder->getUserFolder('test1'); + $fileId = $userFolder->getId(); + + //Now insert cyclic share + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('file'), + 'item_source' => $qb->createNamedParameter($fileId), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter($fileId), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + //Add test user + $user2 = $this->userManager->createUser('test2', 'test2'); + $userFolder = $this->rootFolder->getUserFolder('test2'); + $folder = $userFolder->newFolder('foo'); + $fileId = $folder->getId(); + + //Now insert cyclic share + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('file'), + 'item_source' => $qb->createNamedParameter($fileId), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter($fileId), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $this->repair->run($this->outputMock); + + //Verify + $qb = $this->connection->getQueryBuilder(); + $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count') + ->from('share'); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + $count = (int)$data['count']; + + $this->assertEquals(1, $count); + + $user1->delete(); + $user2->delete(); + } +} From 7aa825f7dce21791f17dbfe7a516f9958d06102e Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 17 Jun 2016 11:08:26 +0200 Subject: [PATCH 13/23] Revert "[stable9] Remove shares of the root folder" (#25157) --- lib/private/repair.php | 2 - lib/private/repair/removerootshares.php | 145 ---------------- tests/lib/repair/removerootsharestest.php | 194 ---------------------- 3 files changed, 341 deletions(-) delete mode 100644 lib/private/repair/removerootshares.php delete mode 100644 tests/lib/repair/removerootsharestest.php diff --git a/lib/private/repair.php b/lib/private/repair.php index b2a1e5a070..098d4e7eb9 100644 --- a/lib/private/repair.php +++ b/lib/private/repair.php @@ -48,7 +48,6 @@ use OC\Repair\RepairMimeTypes; use OC\Repair\SearchLuceneTables; use OC\Repair\UpdateOutdatedOcsIds; use OC\Repair\RepairInvalidShares; -use OC\Repair\RemoveRootShares; class Repair extends BasicEmitter { /** @@ -119,7 +118,6 @@ class Repair extends BasicEmitter { new UpdateOutdatedOcsIds(\OC::$server->getConfig()), new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new AvatarPermissions(\OC::$server->getDatabaseConnection()), - new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getRootFolder()), ]; } diff --git a/lib/private/repair/removerootshares.php b/lib/private/repair/removerootshares.php deleted file mode 100644 index bf7061c083..0000000000 --- a/lib/private/repair/removerootshares.php +++ /dev/null @@ -1,145 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -namespace OC\Repair; - -use OCP\Files\IRootFolder; -use OCP\IDBConnection; -use OCP\IUser; -use OCP\IUserManager; -use OCP\Migration\IOutput; - -/** - * Class RemoveRootShares - * - * @package OC\Repair - */ -class RemoveRootShares implements \OC\RepairStep { - - /** @var IDBConnection */ - protected $connection; - - /** @var IUserManager */ - protected $userManager; - - /** @var IRootFolder */ - protected $rootFolder; - - /** - * RemoveRootShares constructor. - * - * @param IDBConnection $connection - * @param IUserManager $userManager - * @param IRootFolder $rootFolder - */ - public function __construct(IDBConnection $connection, - IUserManager $userManager, - IRootFolder $rootFolder) { - $this->connection = $connection; - $this->userManager = $userManager; - $this->rootFolder = $rootFolder; - } - - /** - * @return string - */ - public function getName() { - return 'Remove shares of a users root folder'; - } - - public function run() { - if ($this->rootSharesExist()) { - $this->removeRootShares(); - } - } - - private function removeRootShares() { - $function = function(IUser $user) { - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); - $fileId = $userFolder->getId(); - - $qb = $this->connection->getQueryBuilder(); - $qb->delete('share') - ->where($qb->expr()->eq('file_source', $qb->createNamedParameter($fileId))) - ->andWhere($qb->expr()->orX( - $qb->expr()->eq('item_type', $qb->expr()->literal('file')), - $qb->expr()->eq('item_type', $qb->expr()->literal('folder')) - )); - - $qb->execute(); - }; - - $userCount = $this->countUsers(); - - $this->userManager->callForAllUsers($function); - } - - /** - * Count all the users - * - * @return int - */ - private function countUsers() { - $allCount = $this->userManager->countUsers(); - - $totalCount = 0; - foreach ($allCount as $backend => $count) { - $totalCount += $count; - } - - return $totalCount; - } - - /** - * Verify if this repair steps is required - * It *should* not be necessary in most cases and it can be very - * costly. - * - * @return bool - */ - private function rootSharesExist() { - $qb = $this->connection->getQueryBuilder(); - $qb2 = $this->connection->getQueryBuilder(); - - $qb->select('fileid') - ->from('filecache') - ->where($qb->expr()->eq('path', $qb->expr()->literal('files'))); - - $qb2->select('id') - ->from('share') - ->where($qb2->expr()->in('file_source', $qb2->createFunction($qb->getSQL()))) - ->andWhere($qb2->expr()->orX( - $qb2->expr()->eq('item_type', $qb->expr()->literal('file')), - $qb2->expr()->eq('item_type', $qb->expr()->literal('folder')) - )) - ->setMaxResults(1); - - $cursor = $qb2->execute(); - $data = $cursor->fetch(); - $cursor->closeCursor(); - - if ($data === false) { - return false; - } - - return true; - } -} - diff --git a/tests/lib/repair/removerootsharestest.php b/tests/lib/repair/removerootsharestest.php deleted file mode 100644 index bf255fc7e9..0000000000 --- a/tests/lib/repair/removerootsharestest.php +++ /dev/null @@ -1,194 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -namespace Test\Repair; - -use OC\Repair\RemoveRootShares; -use OCP\Files\IRootFolder; -use OCP\IDBConnection; -use OCP\IUserManager; -use OCP\Migration\IOutput; -use Test\Traits\UserTrait; - -/** - * Class RemoveOldSharesTest - * - * @package Test\Repair - * @group DB - */ -class RemoveRootSharesTest extends \Test\TestCase { - use UserTrait; - - /** @var RemoveRootShares */ - protected $repair; - - /** @var IDBConnection */ - protected $connection; - - /** @var IOutput */ - private $outputMock; - - /** @var IUserManager */ - private $userManager; - - /** @var IRootFolder */ - private $rootFolder; - - protected function setUp() { - parent::setUp(); - - $this->outputMock = $this->getMockBuilder('\OCP\Migration\IOutput') - ->disableOriginalConstructor() - ->getMock(); - - $this->userManager = \OC::$server->getUserManager(); - $this->rootFolder = \OC::$server->getRootFolder(); - - $this->connection = \OC::$server->getDatabaseConnection(); - $this->repair = new RemoveRootShares($this->connection, $this->userManager, $this->rootFolder); - } - - protected function tearDown() { - $qb = $this->connection->getQueryBuilder(); - $qb->delete('share'); - $qb->execute(); - - return parent::tearDown(); - } - - public function testRootSharesExist() { - //Add test user - $user = $this->userManager->createUser('test', 'test'); - $userFolder = $this->rootFolder->getUserFolder('test'); - $fileId = $userFolder->getId(); - - //Now insert cyclic share - $qb = $this->connection->getQueryBuilder(); - $qb->insert('share') - ->values([ - 'share_type' => $qb->createNamedParameter(0), - 'share_with' => $qb->createNamedParameter('foo'), - 'uid_owner' => $qb->createNamedParameter('owner'), - 'item_type' => $qb->createNamedParameter('file'), - 'item_source' => $qb->createNamedParameter($fileId), - 'item_target' => $qb->createNamedParameter('/target'), - 'file_source' => $qb->createNamedParameter($fileId), - 'file_target' => $qb->createNamedParameter('/target'), - 'permissions' => $qb->createNamedParameter(1), - ]); - $qb->execute(); - - $res = $this->invokePrivate($this->repair, 'rootSharesExist', []); - $this->assertTrue($res); - - $user->delete(); - } - - public function testRootSharesDontExist() { - //Add test user - $user = $this->userManager->createUser('test', 'test'); - $userFolder = $this->rootFolder->getUserFolder('test'); - $fileId = $userFolder->getId(); - - //Now insert cyclic share - $qb = $this->connection->getQueryBuilder(); - $qb->insert('share') - ->values([ - 'share_type' => $qb->createNamedParameter(0), - 'share_with' => $qb->createNamedParameter('foo'), - 'uid_owner' => $qb->createNamedParameter('owner'), - 'item_type' => $qb->createNamedParameter('file'), - 'item_source' => $qb->createNamedParameter($fileId+1), - 'item_target' => $qb->createNamedParameter('/target'), - 'file_source' => $qb->createNamedParameter($fileId+1), - 'file_target' => $qb->createNamedParameter('/target'), - 'permissions' => $qb->createNamedParameter(1), - ]); - $qb->execute(); - - $res = $this->invokePrivate($this->repair, 'rootSharesExist', []); - $this->assertFalse($res); - - $user->delete(); - } - - public function testRun() { - //Add test user - $user1 = $this->userManager->createUser('test1', 'test1'); - $userFolder = $this->rootFolder->getUserFolder('test1'); - $fileId = $userFolder->getId(); - - //Now insert cyclic share - $qb = $this->connection->getQueryBuilder(); - $qb->insert('share') - ->values([ - 'share_type' => $qb->createNamedParameter(0), - 'share_with' => $qb->createNamedParameter('foo'), - 'uid_owner' => $qb->createNamedParameter('owner'), - 'item_type' => $qb->createNamedParameter('file'), - 'item_source' => $qb->createNamedParameter($fileId), - 'item_target' => $qb->createNamedParameter('/target'), - 'file_source' => $qb->createNamedParameter($fileId), - 'file_target' => $qb->createNamedParameter('/target'), - 'permissions' => $qb->createNamedParameter(1), - ]); - $qb->execute(); - - //Add test user - $user2 = $this->userManager->createUser('test2', 'test2'); - $userFolder = $this->rootFolder->getUserFolder('test2'); - $folder = $userFolder->newFolder('foo'); - $fileId = $folder->getId(); - - //Now insert cyclic share - $qb = $this->connection->getQueryBuilder(); - $qb->insert('share') - ->values([ - 'share_type' => $qb->createNamedParameter(0), - 'share_with' => $qb->createNamedParameter('foo'), - 'uid_owner' => $qb->createNamedParameter('owner'), - 'item_type' => $qb->createNamedParameter('file'), - 'item_source' => $qb->createNamedParameter($fileId), - 'item_target' => $qb->createNamedParameter('/target'), - 'file_source' => $qb->createNamedParameter($fileId), - 'file_target' => $qb->createNamedParameter('/target'), - 'permissions' => $qb->createNamedParameter(1), - ]); - $qb->execute(); - - $this->repair->run($this->outputMock); - - //Verify - $qb = $this->connection->getQueryBuilder(); - $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count') - ->from('share'); - - $cursor = $qb->execute(); - $data = $cursor->fetch(); - $cursor->closeCursor(); - - $count = (int)$data['count']; - - $this->assertEquals(1, $count); - - $user1->delete(); - $user2->delete(); - } -} From 2d7bbf4a63722ea4be7517356301859c9fc915f3 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 8 Mar 2016 16:12:17 +0100 Subject: [PATCH 14/23] Do not recurse link share fetching * Might fix an issue on oracle --- lib/private/share20/manager.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 9e04d57b28..7a48de6a11 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -906,6 +906,11 @@ class Manager implements IManager { break; } + // If there was no limit on the select we are done + if ($limit === -1) { + break; + } + $offset += $added; // Fetch again $limit shares From 0e3682d810d9d5ddeb714ba337976cec5f7b2010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 17 Jun 2016 12:46:28 +0200 Subject: [PATCH 15/23] Capped cache for user config --- lib/private/allconfig.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/private/allconfig.php b/lib/private/allconfig.php index b4888fde02..e982b06473 100644 --- a/lib/private/allconfig.php +++ b/lib/private/allconfig.php @@ -27,6 +27,7 @@ */ namespace OC; +use OC\Cache\CappedMemoryCache; use OCP\IDBConnection; use OCP\PreConditionNotMetException; @@ -58,14 +59,15 @@ class AllConfig implements \OCP\IConfig { * - deleteAllUserValues * - deleteAppFromAllUsers * - * @var array $userCache + * @var CappedMemoryCache $userCache */ - private $userCache = array(); + private $userCache; /** * @param SystemConfig $systemConfig */ function __construct(SystemConfig $systemConfig) { + $this->userCache = new CappedMemoryCache(); $this->systemConfig = $systemConfig; } From 0129437cd50a5099d27efe4a16cc9e93fc36b4f6 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 20 Jun 2016 12:02:04 +0200 Subject: [PATCH 16/23] Make getShareFolder use given view instead of static FS (#25150) --- apps/files_sharing/lib/helper.php | 12 ++++++++---- apps/files_sharing/lib/sharedmount.php | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/files_sharing/lib/helper.php b/apps/files_sharing/lib/helper.php index ee6af9939e..612df1b207 100644 --- a/apps/files_sharing/lib/helper.php +++ b/apps/files_sharing/lib/helper.php @@ -302,19 +302,23 @@ class Helper { /** * get default share folder * + * @param \OC\Files\View * @return string */ - public static function getShareFolder() { + public static function getShareFolder($view = null) { + if ($view === null) { + $view = Filesystem::getView(); + } $shareFolder = \OC::$server->getConfig()->getSystemValue('share_folder', '/'); $shareFolder = Filesystem::normalizePath($shareFolder); - if (!Filesystem::file_exists($shareFolder)) { + if (!$view->file_exists($shareFolder)) { $dir = ''; $subdirs = explode('/', $shareFolder); foreach ($subdirs as $subdir) { $dir = $dir . '/' . $subdir; - if (!Filesystem::is_dir($dir)) { - Filesystem::mkdir($dir); + if (!$view->is_dir($dir)) { + $view->mkdir($dir); } } } diff --git a/apps/files_sharing/lib/sharedmount.php b/apps/files_sharing/lib/sharedmount.php index 1e554cc594..3a65794b60 100644 --- a/apps/files_sharing/lib/sharedmount.php +++ b/apps/files_sharing/lib/sharedmount.php @@ -75,7 +75,7 @@ class SharedMount extends MountPoint implements MoveableMount { $parent = dirname($share['file_target']); if (!$this->recipientView->is_dir($parent)) { - $parent = Helper::getShareFolder(); + $parent = Helper::getShareFolder($this->recipientView); } $newMountPoint = \OCA\Files_Sharing\Helper::generateUniqueTarget( From 2f61c2963c6c777e402adf7424e08d4029b25d1d Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 17 Jun 2016 11:00:09 +0200 Subject: [PATCH 17/23] Delay files_sharing's registerMountProviders This moves registerMountProviders until after the sharing backends were registered. In some situations registerMountProviders will trigger listeners which might require filesystem access which itself would mount shares, which itself requires the sharing backends to be initialized. --- apps/files_sharing/appinfo/app.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 29202c15b2..628921c324 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -41,8 +41,6 @@ $l = \OC::$server->getL10N('files_sharing'); \OC::$CLASSPATH['OCA\Files\Share\Maintainer'] = 'files_sharing/lib/maintainer.php'; \OC::$CLASSPATH['OCA\Files\Share\Proxy'] = 'files_sharing/lib/proxy.php'; -$application = new Application(); -$application->registerMountProviders(); \OCP\App::registerAdmin('files_sharing', 'settings-admin'); \OCP\App::registerPersonal('files_sharing', 'settings-personal'); @@ -52,6 +50,9 @@ $application->registerMountProviders(); \OCP\Share::registerBackend('file', 'OC_Share_Backend_File'); \OCP\Share::registerBackend('folder', 'OC_Share_Backend_Folder', 'file'); +$application = new Application(); +$application->registerMountProviders(); + $eventDispatcher = \OC::$server->getEventDispatcher(); $eventDispatcher->addListener( 'OCA\Files::loadAdditionalScripts', From 11c1d399afa1a18e817482b38eb9aeb4d6492b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 10 Jun 2016 16:40:44 +0200 Subject: [PATCH 18/23] Fix null pointer exception in user_ldap --- apps/user_ldap/lib/access.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index f92ded6479..dd4aeee3b2 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -730,7 +730,14 @@ class Access extends LDAPUtility implements user\IUserTools { $user->unmark(); $user = $this->userManager->get($ocName); } - $user->processAttributes($userRecord); + if ($user !== null) { + $user->processAttributes($userRecord); + } else { + \OC::$server->getLogger()->debug( + "The ldap user manager returned null for $ocName", + ['app'=>'user_ldap'] + ); + } } } From fa9ba64552a43448d7394d3e11a60997b00eca48 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 20 Jun 2016 22:11:05 +0200 Subject: [PATCH 19/23] Catch exceptions while creating shared mounts --- apps/files_sharing/appinfo/application.php | 3 +- apps/files_sharing/lib/mountprovider.php | 35 +++++++++++++++------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/apps/files_sharing/appinfo/application.php b/apps/files_sharing/appinfo/application.php index 64c7517456..311250ce19 100644 --- a/apps/files_sharing/appinfo/application.php +++ b/apps/files_sharing/appinfo/application.php @@ -111,7 +111,8 @@ class Application extends App { /** @var \OCP\IServerContainer $server */ $server = $c->query('ServerContainer'); return new MountProvider( - $server->getConfig() + $server->getConfig(), + $server->getLogger() ); }); diff --git a/apps/files_sharing/lib/mountprovider.php b/apps/files_sharing/lib/mountprovider.php index 4a60e44bb2..d1d54a21ab 100644 --- a/apps/files_sharing/lib/mountprovider.php +++ b/apps/files_sharing/lib/mountprovider.php @@ -27,6 +27,7 @@ use OC\User\NoUserException; use OCP\Files\Config\IMountProvider; use OCP\Files\Storage\IStorageFactory; use OCP\IConfig; +use OCP\ILogger; use OCP\IUser; class MountProvider implements IMountProvider { @@ -36,10 +37,17 @@ class MountProvider implements IMountProvider { protected $config; /** - * @param \OCP\IConfig $config + * @var ILogger */ - public function __construct(IConfig $config) { + protected $logger; + + /** + * @param \OCP\IConfig $config + * @param ILogger $logger + */ + public function __construct(IConfig $config, ILogger $logger) { $this->config = $config; + $this->logger = $logger; } @@ -57,15 +65,20 @@ class MountProvider implements IMountProvider { }); $shares = array_map(function ($share) use ($user, $storageFactory) { - return new SharedMount( - '\OC\Files\Storage\Shared', - '/' . $user->getUID() . '/' . $share['file_target'], - array( - 'share' => $share, - 'user' => $user->getUID() - ), - $storageFactory - ); + try { + return new SharedMount( + '\OC\Files\Storage\Shared', + '/' . $user->getUID() . '/' . $share['file_target'], + array( + 'share' => $share, + 'user' => $user->getUID() + ), + $storageFactory + ); + } catch (\Exception $e) { + $this->logger->logException($e); + $this->logger->error('Error while trying to create shared mount'); + } }, $shares); // array_filter removes the null values from the array return array_filter($shares); From bf7a08f62dafd24932b93ffd213f4e40f151ced0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Villafa=C3=B1ez?= Date: Tue, 21 Jun 2016 15:04:08 +0200 Subject: [PATCH 20/23] dd support to know where the storage test comes from (#25166) --- .../controller/globalstoragescontroller.php | 6 ++++-- apps/files_external/controller/storagescontroller.php | 11 +++++++---- .../controller/userglobalstoragescontroller.php | 11 +++++++---- .../controller/userstoragescontroller.php | 10 ++++++---- apps/files_external/js/settings.js | 5 ++++- apps/files_external/js/statusmanager.js | 1 + apps/files_external/lib/config.php | 4 ++-- apps/files_external/tests/js/settingsSpec.js | 3 ++- 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/apps/files_external/controller/globalstoragescontroller.php b/apps/files_external/controller/globalstoragescontroller.php index b443cf4ea8..dc7e8c0c24 100644 --- a/apps/files_external/controller/globalstoragescontroller.php +++ b/apps/files_external/controller/globalstoragescontroller.php @@ -131,6 +131,7 @@ class GlobalStoragesController extends StoragesController { * @param array $applicableUsers users for which to mount the storage * @param array $applicableGroups groups for which to mount the storage * @param int $priority priority + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse */ @@ -143,7 +144,8 @@ class GlobalStoragesController extends StoragesController { $mountOptions, $applicableUsers, $applicableGroups, - $priority + $priority, + $testOnly = true ) { $storage = $this->createStorage( $mountPoint, @@ -176,7 +178,7 @@ class GlobalStoragesController extends StoragesController { ); } - $this->updateStorageStatus($storage); + $this->updateStorageStatus($storage, $testOnly); return new DataResponse( $storage, diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php index 09b8310470..b26e85cc9a 100644 --- a/apps/files_external/controller/storagescontroller.php +++ b/apps/files_external/controller/storagescontroller.php @@ -240,8 +240,9 @@ abstract class StoragesController extends Controller { * on whether the remote storage is available or not. * * @param StorageConfig $storage storage configuration + * @param bool $testOnly whether to storage should only test the connection or do more things */ - protected function updateStorageStatus(StorageConfig &$storage) { + protected function updateStorageStatus(StorageConfig &$storage, $testOnly = true) { try { $this->manipulateStorageConfig($storage); @@ -252,7 +253,8 @@ abstract class StoragesController extends Controller { \OC_Mount_Config::getBackendStatus( $backend->getStorageClass(), $storage->getBackendOptions(), - false + false, + $testOnly ) ); } catch (InsufficientDataForMeaningfulAnswerException $e) { @@ -293,14 +295,15 @@ abstract class StoragesController extends Controller { * Get an external storage entry. * * @param int $id storage id + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse */ - public function show($id) { + public function show($id, $testOnly = true) { try { $storage = $this->service->getStorage($id); - $this->updateStorageStatus($storage); + $this->updateStorageStatus($storage, $testOnly); } catch (NotFoundException $e) { return new DataResponse( [ diff --git a/apps/files_external/controller/userglobalstoragescontroller.php b/apps/files_external/controller/userglobalstoragescontroller.php index 36c3740eed..f89022ee46 100644 --- a/apps/files_external/controller/userglobalstoragescontroller.php +++ b/apps/files_external/controller/userglobalstoragescontroller.php @@ -106,15 +106,16 @@ class UserGlobalStoragesController extends StoragesController { * Get an external storage entry. * * @param int $id storage id + * @param bool $testOnly whether to storage should only test the connection or do more things * @return DataResponse * * @NoAdminRequired */ - public function show($id) { + public function show($id, $testOnly = true) { try { $storage = $this->service->getStorage($id); - $this->updateStorageStatus($storage); + $this->updateStorageStatus($storage, $testOnly); } catch (NotFoundException $e) { return new DataResponse( [ @@ -138,6 +139,7 @@ class UserGlobalStoragesController extends StoragesController { * * @param int $id storage id * @param array $backendOptions backend-specific options + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse * @@ -145,7 +147,8 @@ class UserGlobalStoragesController extends StoragesController { */ public function update( $id, - $backendOptions + $backendOptions, + $testOnly = true ) { try { $storage = $this->service->getStorage($id); @@ -170,7 +173,7 @@ class UserGlobalStoragesController extends StoragesController { ); } - $this->updateStorageStatus($storage); + $this->updateStorageStatus($storage, $testOnly); $this->sanitizeStorage($storage); return new DataResponse( diff --git a/apps/files_external/controller/userstoragescontroller.php b/apps/files_external/controller/userstoragescontroller.php index e53ea21f00..8ab2234122 100644 --- a/apps/files_external/controller/userstoragescontroller.php +++ b/apps/files_external/controller/userstoragescontroller.php @@ -104,8 +104,8 @@ class UserStoragesController extends StoragesController { * * {@inheritdoc} */ - public function show($id) { - return parent::show($id); + public function show($id, $testOnly = true) { + return parent::show($id, $testOnly); } /** @@ -162,6 +162,7 @@ class UserStoragesController extends StoragesController { * @param string $authMechanism authentication mechanism identifier * @param array $backendOptions backend-specific options * @param array $mountOptions backend-specific mount options + * @param bool $testOnly whether to storage should only test the connection or do more things * * @return DataResponse * @@ -173,7 +174,8 @@ class UserStoragesController extends StoragesController { $backend, $authMechanism, $backendOptions, - $mountOptions + $mountOptions, + $testOnly = true ) { $storage = $this->createStorage( $mountPoint, @@ -203,7 +205,7 @@ class UserStoragesController extends StoragesController { ); } - $this->updateStorageStatus($storage); + $this->updateStorageStatus($storage, $testOnly); return new DataResponse( $storage, diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 0b33458bec..6262245688 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -305,7 +305,8 @@ StorageConfig.prototype = { mountPoint: this.mountPoint, backend: this.backend, authMechanism: this.authMechanism, - backendOptions: this.backendOptions + backendOptions: this.backendOptions, + testOnly: true }; if (this.id) { data.id = this.id; @@ -332,6 +333,7 @@ StorageConfig.prototype = { $.ajax({ type: 'GET', url: OC.generateUrl(this._url + '/{id}', {id: this.id}), + data: {'testOnly': true}, success: options.success, error: options.error }); @@ -911,6 +913,7 @@ MountConfigListView.prototype = _.extend({ $.ajax({ type: 'GET', url: OC.generateUrl('apps/files_external/userglobalstorages'), + data: {'testOnly': true}, contentType: 'application/json', success: function(result) { var onCompletion = jQuery.Deferred(); diff --git a/apps/files_external/js/statusmanager.js b/apps/files_external/js/statusmanager.js index 33d2ea104b..c51562558c 100644 --- a/apps/files_external/js/statusmanager.js +++ b/apps/files_external/js/statusmanager.js @@ -78,6 +78,7 @@ OCA.External.StatusManager = { defObj = $.ajax({ type: 'GET', url: OC.webroot + '/index.php/apps/files_external/' + ((mountData.type === 'personal') ? 'userstorages' : 'userglobalstorages') + '/' + mountData.id, + data: {'testOnly': false}, success: function (response) { if (response && response.status === 0) { self.mountStatus[mountData.mount_point] = response; diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 70f8550f39..11a111bd8f 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -215,7 +215,7 @@ class OC_Mount_Config { * @return int see self::STATUS_* * @throws Exception */ - public static function getBackendStatus($class, $options, $isPersonal) { + public static function getBackendStatus($class, $options, $isPersonal, $testOnly = true) { if (self::$skipTest) { return StorageNotAvailableException::STATUS_SUCCESS; } @@ -228,7 +228,7 @@ class OC_Mount_Config { $storage = new $class($options); try { - $result = $storage->test($isPersonal); + $result = $storage->test($isPersonal, $testOnly); $storage->setAvailability($result); if ($result) { return StorageNotAvailableException::STATUS_SUCCESS; diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js index 462407e954..e4ee7a61aa 100644 --- a/apps/files_external/tests/js/settingsSpec.js +++ b/apps/files_external/tests/js/settingsSpec.js @@ -223,7 +223,8 @@ describe('OCA.External.Settings tests', function() { applicableGroups: [], mountOptions: { 'previews': true - } + }, + testOnly: true }); // TODO: respond and check data-id From 880ff122f11b51585d115892b8ba79b6900237bd Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 22 Jun 2016 15:18:19 +0200 Subject: [PATCH 21/23] Rollback version must also adjust cached size --- apps/files_versions/lib/storage.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/files_versions/lib/storage.php b/apps/files_versions/lib/storage.php index a1069410e7..1062f49cf6 100644 --- a/apps/files_versions/lib/storage.php +++ b/apps/files_versions/lib/storage.php @@ -337,9 +337,16 @@ class Storage { // Restore encrypted version of the old file for the newly restored file // This has to happen manually here since the file is manually copied below $oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion(); + $oldFileInfo = $users_view->getFileInfo($fileToRestore); $newFileInfo = $files_view->getFileInfo($filename); $cache = $newFileInfo->getStorage()->getCache(); - $cache->update($newFileInfo->getId(), ['encrypted' => $oldVersion, 'encryptedVersion' => $oldVersion]); + $cache->update( + $newFileInfo->getId(), [ + 'encrypted' => $oldVersion, + 'encryptedVersion' => $oldVersion, + 'size' => $oldFileInfo->getSize() + ] + ); // rollback if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) { From b6192c39d840c46631cb8e3f4ab69aec4b04a479 Mon Sep 17 00:00:00 2001 From: Roeland Douma Date: Thu, 14 Apr 2016 11:50:27 +0200 Subject: [PATCH 22/23] On mount make sure multiple shares with same target map to unique ones (#23937) Scenario: user0 shares a folder 'foo' with user2 user1 shares a folder 'foo' with user2 user2 logs in Before: show only the 'foo' from user1 After: show both. * Added intergration tests --- apps/files_sharing/lib/mountprovider.php | 17 +++---- apps/files_sharing/lib/sharedmount.php | 47 ++++++++++++++++--- build/integration/features/sharing-v1.feature | 13 +++++ 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/apps/files_sharing/lib/mountprovider.php b/apps/files_sharing/lib/mountprovider.php index d1d54a21ab..b386c6704f 100644 --- a/apps/files_sharing/lib/mountprovider.php +++ b/apps/files_sharing/lib/mountprovider.php @@ -63,24 +63,25 @@ class MountProvider implements IMountProvider { $shares = array_filter($shares, function ($share) { return $share['permissions'] > 0; }); - $shares = array_map(function ($share) use ($user, $storageFactory) { - + $mounts = []; + foreach ($shares as $share) { try { - return new SharedMount( + $mounts[] = new SharedMount( '\OC\Files\Storage\Shared', - '/' . $user->getUID() . '/' . $share['file_target'], - array( + $mounts, + [ 'share' => $share, 'user' => $user->getUID() - ), + ], $storageFactory ); } catch (\Exception $e) { $this->logger->logException($e); $this->logger->error('Error while trying to create shared mount'); } - }, $shares); + } + // array_filter removes the null values from the array - return array_filter($shares); + return array_filter($mounts); } } diff --git a/apps/files_sharing/lib/sharedmount.php b/apps/files_sharing/lib/sharedmount.php index 3a65794b60..8eaf546964 100644 --- a/apps/files_sharing/lib/sharedmount.php +++ b/apps/files_sharing/lib/sharedmount.php @@ -25,6 +25,7 @@ namespace OCA\Files_Sharing; +use OC\Files\Filesystem; use OC\Files\Mount\MountPoint; use OC\Files\Mount\MoveableMount; use OC\Files\View; @@ -50,14 +51,14 @@ class SharedMount extends MountPoint implements MoveableMount { /** * @param string $storage - * @param string $mountpoint + * @param SharedMount[] $mountpoints * @param array|null $arguments * @param \OCP\Files\Storage\IStorageFactory $loader */ - public function __construct($storage, $mountpoint, $arguments = null, $loader = null) { + public function __construct($storage, array $mountpoints, $arguments = null, $loader = null) { $this->user = $arguments['user']; $this->recipientView = new View('/' . $this->user . '/files'); - $newMountPoint = $this->verifyMountPoint($arguments['share']); + $newMountPoint = $this->verifyMountPoint($arguments['share'], $mountpoints); $absMountPoint = '/' . $this->user . '/files' . $newMountPoint; $arguments['ownerView'] = new View('/' . $arguments['share']['uid_owner'] . '/files'); parent::__construct($storage, $absMountPoint, $arguments, $loader); @@ -67,9 +68,10 @@ class SharedMount extends MountPoint implements MoveableMount { * check if the parent folder exists otherwise move the mount point up * * @param array $share + * @param SharedMount[] $mountpoints * @return string */ - private function verifyMountPoint(&$share) { + private function verifyMountPoint(&$share, array $mountpoints) { $mountPoint = basename($share['file_target']); $parent = dirname($share['file_target']); @@ -78,10 +80,10 @@ class SharedMount extends MountPoint implements MoveableMount { $parent = Helper::getShareFolder($this->recipientView); } - $newMountPoint = \OCA\Files_Sharing\Helper::generateUniqueTarget( + $newMountPoint = $this->generateUniqueTarget( \OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint), - [], - $this->recipientView + $this->recipientView, + $mountpoints ); if ($newMountPoint !== $share['file_target']) { @@ -93,6 +95,37 @@ class SharedMount extends MountPoint implements MoveableMount { return $newMountPoint; } + /** + * @param string $path + * @param View $view + * @param SharedMount[] $mountpoints + * @return mixed + */ + private function generateUniqueTarget($path, $view, array $mountpoints) { + $pathinfo = pathinfo($path); + $ext = (isset($pathinfo['extension'])) ? '.'.$pathinfo['extension'] : ''; + $name = $pathinfo['filename']; + $dir = $pathinfo['dirname']; + + // Helper function to find existing mount points + $mountpointExists = function($path) use ($mountpoints) { + foreach ($mountpoints as $mountpoint) { + if ($mountpoint->getShare()['file_target'] === $path) { + return true; + } + } + return false; + }; + + $i = 2; + while ($view->file_exists($path) || $mountpointExists($path)) { + $path = Filesystem::normalizePath($dir . '/' . $name . ' ('.$i.')' . $ext); + $i++; + } + + return $path; + } + /** * update fileTarget in the database if the mount point changed * diff --git a/build/integration/features/sharing-v1.feature b/build/integration/features/sharing-v1.feature index ba535e83aa..56399eefdb 100644 --- a/build/integration/features/sharing-v1.feature +++ b/build/integration/features/sharing-v1.feature @@ -566,3 +566,16 @@ Feature: sharing | path | welcome.txt | | shareType | 3 | Then share ids should match + + Scenario: unique target names for incomming shares + Given user "user0" exists + And user "user1" exists + And user "user2" exists + And user "user0" created a folder "/foo" + And user "user1" created a folder "/foo" + When file "/foo" of user "user0" is shared with user "user2" + And file "/foo" of user "user1" is shared with user "user2" + Then user "user2" should see following elements + | /foo/ | + | /foo%20(2)/ | + From e366ed6485a987cc7a77573bf3e46cba854b07b5 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 23 Jun 2016 11:54:49 +0200 Subject: [PATCH 23/23] Don't reload page in case of auth errors during setup checks If an error occurs during setup checks, do not let the global ajax error handler reload the page. --- core/js/setupchecks.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index ee93781b07..9c601e7bd5 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -76,7 +76,8 @@ $.ajax({ type: 'PROPFIND', url: url, - complete: afterCall + complete: afterCall, + allowAuthErrors: true }); return deferred.promise(); }, @@ -209,7 +210,8 @@ $.ajax({ type: 'GET', url: OC.linkTo('', oc_dataURL+'/htaccesstest.txt?t=' + (new Date()).getTime()), - complete: afterCall + complete: afterCall, + allowAuthErrors: true }); return deferred.promise(); },