diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 1fd204fcd8..96731fa9df 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -36,6 +36,7 @@ use OC\Repair\DropOldJobs; use OC\Repair\OldGroupMembershipShares; use OC\Repair\RemoveGetETagEntries; use OC\Repair\RemoveOldShares; +use OC\Repair\RemoveRootShares; use OC\Repair\SharePropagation; use OC\Repair\SqliteAutoincrement; use OC\Repair\DropOldTables; @@ -136,6 +137,7 @@ class Repair implements IOutput{ new SharePropagation(\OC::$server->getConfig()), new RemoveOldShares(\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..89f797e3ef --- /dev/null +++ b/lib/private/Repair/RemoveRootShares.php @@ -0,0 +1,157 @@ + + * + * @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; +use OCP\Migration\IRepairStep; + +/** + * Class RemoveRootShares + * + * @package OC\Repair + */ +class RemoveRootShares implements IRepairStep { + + /** @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'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + if ($this->rootSharesExist()) { + $this->removeRootShares($output); + } + } + + /** + * @param IOutput $output + */ + private function removeRootShares(IOutput $output) { + $function = function(IUser $user) use ($output) { + $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(); + + $output->advance(); + }; + + $userCount = $this->countUsers(); + $output->startProgress($userCount); + + $this->userManager->callForAllUsers($function); + + $output->finishProgress(); + } + + /** + * 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(); + } +}