diff --git a/apps/files_sharing/api/ocssharewrapper.php b/apps/files_sharing/api/ocssharewrapper.php index 8195e92b29..7f909d413e 100644 --- a/apps/files_sharing/api/ocssharewrapper.php +++ b/apps/files_sharing/api/ocssharewrapper.php @@ -26,22 +26,34 @@ class OCSShareWrapper { * @return Share20OCS */ private function getShare20OCS() { - return new Share20OCS( - new \OC\Share20\Manager( - \OC::$server->getLogger(), - \OC::$server->getConfig(), - new \OC\Share20\DefaultShareProvider( + $manager =new \OC\Share20\Manager( + \OC::$server->getLogger(), + \OC::$server->getConfig(), + \OC::$server->getSecureRandom(), + \OC::$server->getHasher(), + \OC::$server->getMountManager(), + \OC::$server->getGroupManager(), + \OC::$server->getL10N('core') + ); + + $manager->registerProvider('ocdefault', + [ + \OCP\Share::SHARE_TYPE_USER, + \OCP\SHARE::SHARE_TYPE_GROUP, + \OCP\SHARE::SHARE_TYPE_LINK + ], + function() { + return new \OC\Share20\DefaultShareProvider( \OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getGroupManager(), \OC::$server->getRootFolder() - ), - \OC::$server->getSecureRandom(), - \OC::$server->getHasher(), - \OC::$server->getMountManager(), - \OC::$server->getGroupManager(), - \OC::$server->getL10N('core') - ), + ); + } + ); + + return new Share20OCS( + $manager, \OC::$server->getGroupManager(), \OC::$server->getUserManager(), \OC::$server->getRequest(), diff --git a/lib/private/share20/exception/providerexception.php b/lib/private/share20/exception/providerexception.php new file mode 100644 index 0000000000..a14d526658 --- /dev/null +++ b/lib/private/share20/exception/providerexception.php @@ -0,0 +1,27 @@ + + * + * @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\Share20\Exception; + + +class ProviderException extends \Exception { + +} + diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 11faf017f3..d9014e64dc 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -21,6 +21,7 @@ namespace OC\Share20; +use OC\Share20\Exception\ProviderException; use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; @@ -40,8 +41,11 @@ use OC\HintException; */ class Manager { - /** @var IShareProvider[] */ - private $defaultProvider; + /** @var array */ + private $providers; + + /** @var array */ + private $type2provider; /** @var ILogger */ private $logger; @@ -69,7 +73,6 @@ class Manager { * * @param ILogger $logger * @param IConfig $config - * @param IShareProvider $defaultProvider * @param ISecureRandom $secureRandom * @param IHasher $hasher * @param IMountManager $mountManager @@ -79,13 +82,15 @@ class Manager { public function __construct( ILogger $logger, IConfig $config, - IShareProvider $defaultProvider, ISecureRandom $secureRandom, IHasher $hasher, IMountManager $mountManager, IGroupManager $groupManager, IL10N $l ) { + $this->providers = []; + $this->type2provider = []; + $this->logger = $logger; $this->config = $config; $this->secureRandom = $secureRandom; @@ -93,9 +98,95 @@ class Manager { $this->mountManager = $mountManager; $this->groupManager = $groupManager; $this->l = $l; + } - // TEMP SOLUTION JUST TO GET STARTED - $this->defaultProvider = $defaultProvider; + /** + * Register a share provider + * + * @param string $id The id of the share provider + * @param int[] $shareTypes Array containing the share types this provider handles + * @param callable $callback Callback that must return an IShareProvider instance + * @throws ProviderException + */ + public function registerProvider($id, $shareTypes, callable $callback) { + // Providers must have an unique id + if (isset($this->providers[$id])) { + throw new ProviderException('A share provider with the id \''. $id . '\' is already registered'); + } + + if ($shareTypes === []) { + throw new ProviderException('shareTypes can\'t be an empty array'); + } + + foreach($shareTypes as $shareType) { + // We only have 1 provder per share type + if (isset($this->type2provider[$shareType])) { + throw new ProviderException('The share provider ' . $this->type2provider[$shareType] . ' is already registered for share type ' . $shareType); + } + + /* + * We only allow providers that provider + * user- / group- / link- / federated-shares + */ + if ($shareType !== \OCP\Share::SHARE_TYPE_USER && + $shareType !== \OCP\Share::SHARE_TYPE_GROUP && + $shareType !== \OCP\Share::SHARE_TYPE_LINK && + $shareType !== \OCP\Share::SHARE_TYPE_REMOTE) { + throw new ProviderException('Cannot register provider for share type ' . $shareType); + //Throw exception + } + } + + // Add the provider + $this->providers[$id] = [ + 'id' => $id, + 'callback' => $callback, + 'provider' => null, + 'shareTypes' => $shareTypes, + ]; + + // Update the type mapping + foreach ($shareTypes as $shareType) { + $this->type2provider[$shareType] = $id; + } + } + + /** + * @param string $id + * @return IShareProvider + * @throws ProviderException + */ + private function getProvider($id) { + if (!isset($this->providers[$id])) { + throw new ProviderException('No provider with id ' . $id . ' found'); + } + + if ($this->providers[$id]['provider'] === null) { + // First time using this provider + $provider = call_user_func($this->providers[$id]['callback']); + + // Make sure a proper provider is returned + if (!($provider instanceof IShareProvider)) { + throw new ProviderException('Callback does not return an IShareProvider instance for provider with id ' . $id); + } + + $this->providers[$id]['provider'] = $provider; + } + + return $this->providers[$id]['provider']; + } + + /** + * @param int $shareType + * @return IShareProvider + * @throws ProviderException + */ + private function getProviderForType($shareType) { + if (!isset($this->type2provider[$shareType])) { + throw new ProviderException('No share provider registered for share type ' . $shareType); + } + + return $this->getProvider($this->type2provider[$shareType]); } /** @@ -248,7 +339,7 @@ class Manager { /** - * Check for pre share requirements for use shares + * Check for pre share requirements for user shares * * @param IShare $share * @throws \Exception @@ -271,7 +362,8 @@ class Manager { * * Also this is not what we want in the future.. then we want to squash identical shares. */ - $existingShares = $this->defaultProvider->getSharesByPath($share->getPath()); + $provider = $this->getProviderForType(\OCP\Share::SHARE_TYPE_USER); + $existingShares = $provider->getSharesByPath($share->getPath()); foreach($existingShares as $existingShare) { // Identical share already existst if ($existingShare->getSharedWith() === $share->getSharedWith()) { @@ -306,7 +398,8 @@ class Manager { * * Also this is not what we want in the future.. then we want to squash identical shares. */ - $existingShares = $this->defaultProvider->getSharesByPath($share->getPath()); + $provider = $this->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP); + $existingShares = $provider->getSharesByPath($share->getPath()); foreach($existingShares as $existingShare) { if ($existingShare->getSharedWith() === $share->getSharedWith()) { throw new \Exception('Path already shared with this group'); @@ -456,7 +549,8 @@ class Manager { throw new \Exception($error); } - $share = $this->defaultProvider->create($share); + $provider = $this->getProviderForType($share->getShareType()); + $share = $provider->create($share); // Post share hook $postHookData = [ @@ -492,12 +586,18 @@ class Manager { */ protected function deleteChildren(IShare $share) { $deletedShares = []; - foreach($this->defaultProvider->getChildren($share) as $child) { - $deletedChildren = $this->deleteChildren($child); - $deletedShares = array_merge($deletedShares, $deletedChildren); - $this->defaultProvider->delete($child); - $deletedShares[] = $child; + $providerIds = array_keys($this->providers); + + foreach($providerIds as $providerId) { + $provider = $this->getProvider($providerId); + foreach ($provider->getChildren($share) as $child) { + $deletedChildren = $this->deleteChildren($child); + $deletedShares = array_merge($deletedShares, $deletedChildren); + + $provider->delete($child); + $deletedShares[] = $child; + } } return $deletedShares; @@ -549,7 +649,8 @@ class Manager { $deletedShares = $this->deleteChildren($share); // Do the actual delete - $this->defaultProvider->delete($share); + $provider = $this->getProviderForType($share->getShareType()); + $provider->delete($share); // All the deleted shares caused by this delete $deletedShares[] = $share; @@ -588,7 +689,26 @@ class Manager { throw new ShareNotFound(); } - $share = $this->defaultProvider->getShareById($id); + //FIXME ids need to become proper providerid:shareid eventually + + $providerIds = array_keys($this->providers); + + $share = null; + foreach ($providerIds as $providerId) { + $provider = $this->getProvider($providerId); + + try { + $share = $provider->getShareById($id); + $found = true; + break; + } catch (ShareNotFound $e) { + // Ignore + } + } + + if ($share === null) { + throw new ShareNotFound(); + } return $share; } diff --git a/tests/lib/share20/managertest.php b/tests/lib/share20/managertest.php index 57e7e11071..a83da1187c 100644 --- a/tests/lib/share20/managertest.php +++ b/tests/lib/share20/managertest.php @@ -86,13 +86,22 @@ class ManagerTest extends \Test\TestCase { $this->manager = new Manager( $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, $this->groupManager, $this->l ); + $this->manager->registerProvider('tmp', + [ + \OCP\SHARE::SHARE_TYPE_USER, + \OCP\Share::SHARE_TYPE_GROUP, + \OCP\Share::SHARE_TYPE_LINK + ], + function() { + return $this->defaultProvider; + } + ); } /** @@ -133,7 +142,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -143,6 +151,13 @@ class ManagerTest extends \Test\TestCase { ->setMethods(['getShareById', 'deleteChildren']) ->getMock(); + $manager->registerProvider('tmp', + [$shareType], + function() { + return $this->defaultProvider; + } + ); + $sharedBy = $this->getMock('\OCP\IUser'); $sharedBy->method('getUID')->willReturn('sharedBy'); @@ -224,7 +239,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -234,6 +248,15 @@ class ManagerTest extends \Test\TestCase { ->setMethods(['getShareById']) ->getMock(); + $manager->registerProvider('tmp', + [ + \OCP\Share::SHARE_TYPE_USER + ], + function() { + return $this->defaultProvider; + } + ); + $sharedBy1 = $this->getMock('\OCP\IUser'); $sharedBy1->method('getUID')->willReturn('sharedBy1'); $sharedBy2 = $this->getMock('\OCP\IUser'); @@ -369,7 +392,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -379,6 +401,13 @@ class ManagerTest extends \Test\TestCase { ->setMethods(['deleteShare']) ->getMock(); + $manager->registerProvider('tmp', + [\OCP\Share::SHARE_TYPE_USER], + function() { + return $this->defaultProvider; + } + ); + $share = $this->getMock('\OC\Share20\IShare'); $child1 = $this->getMock('\OC\Share20\IShare'); @@ -1156,7 +1185,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -1185,7 +1213,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -1205,7 +1232,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -1215,6 +1241,13 @@ class ManagerTest extends \Test\TestCase { ->setMethods(['canShare', 'generalCreateChecks', 'userCreateChecks', 'pathCreateChecks']) ->getMock(); + $manager->registerProvider('tmp', + [\OCP\Share::SHARE_TYPE_USER], + function() { + return $this->defaultProvider; + } + ); + $sharedWith = $this->getMock('\OCP\IUser'); $sharedBy = $this->getMock('\OCP\IUser'); $shareOwner = $this->getMock('\OCP\IUser'); @@ -1267,7 +1300,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -1277,6 +1309,13 @@ class ManagerTest extends \Test\TestCase { ->setMethods(['canShare', 'generalCreateChecks', 'groupCreateChecks', 'pathCreateChecks']) ->getMock(); + $manager->registerProvider('tmp', + [\OCP\Share::SHARE_TYPE_GROUP], + function() { + return $this->defaultProvider; + } + ); + $sharedWith = $this->getMock('\OCP\IGroup'); $sharedBy = $this->getMock('\OCP\IUser'); $shareOwner = $this->getMock('\OCP\IUser'); @@ -1329,7 +1368,7 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, + $this->secureRandom, $this->hasher, $this->mountManager, @@ -1346,6 +1385,13 @@ class ManagerTest extends \Test\TestCase { ]) ->getMock(); + $manager->registerProvider('tmp', + [\OCP\Share::SHARE_TYPE_LINK], + function() { + return $this->defaultProvider; + } + ); + $sharedBy = $this->getMock('\OCP\IUser'); $sharedBy->method('getUID')->willReturn('sharedBy'); $shareOwner = $this->getMock('\OCP\IUser'); @@ -1470,7 +1516,6 @@ class ManagerTest extends \Test\TestCase { ->setConstructorArgs([ $this->logger, $this->config, - $this->defaultProvider, $this->secureRandom, $this->hasher, $this->mountManager, @@ -1485,6 +1530,13 @@ class ManagerTest extends \Test\TestCase { ]) ->getMock(); + $manager->registerProvider('tmp', + [\OCP\Share::SHARE_TYPE_USER], + function() { + return $this->defaultProvider; + } + ); + $sharedWith = $this->getMock('\OCP\IUser'); $sharedBy = $this->getMock('\OCP\IUser'); $shareOwner = $this->getMock('\OCP\IUser'); @@ -1530,6 +1582,184 @@ class ManagerTest extends \Test\TestCase { $manager->createShare($share); } + + public function dataRegisterProvider() { + return [ + [[ \OCP\Share::SHARE_TYPE_REMOTE,]], + [[ \OCP\Share::SHARE_TYPE_LINK, ]], + [[ \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE,]], + [[ \OCP\Share::SHARE_TYPE_GROUP, ]], + [[ \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_REMOTE,]], + [[ \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, ]], + [[ \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE,]], + [[ \OCP\Share::SHARE_TYPE_GROUP, ]], + [[\OCP\Share::SHARE_TYPE_USER, ]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_REMOTE,]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK, ]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE,]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, ]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_REMOTE,]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, ]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE,]], + ]; + } + + /** + * @dataProvider dataRegisterProvider + * @param int[] $shareTypes + */ + public function testRegisterProvider($shareTypes) { + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l + ); + + $provider = $this->getMock('OC\Share20\IShareProvider'); + $manager->registerProvider('foo', $shareTypes, function() use ($provider) { + return $provider; + }); + + $this->assertEquals($provider, $this->invokePrivate($manager, 'getProvider', ['foo'])); + foreach ($shareTypes as $shareType) { + $this->assertEquals($provider, $this->invokePrivate($manager, 'getProviderForType', [$shareType])); + } + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage A share provider with the id 'foo' is already registered + */ + public function testRegisterProviderDuplicateId() { + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l + ); + + $provider = $this->getMock('OC\Share20\IShareProvider'); + $manager->registerProvider('foo', [\OCP\Share::SHARE_TYPE_USER], function() use ($provider) { + return $provider; + }); + $manager->registerProvider('foo', [\OCP\Share::SHARE_TYPE_USER], function() use ($provider) { + return $provider; + }); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage shareTypes can't be an empty array + */ + public function testRegisterProviderEmptyShareTypes() { + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l + ); + + $provider = $this->getMock('OC\Share20\IShareProvider'); + $manager->registerProvider('foo', [], function() use ($provider) { + return $provider; + }); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage The share provider foo is already registered for share type 0 + */ + public function testRegisterProviderSameType() { + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l + ); + + $provider = $this->getMock('OC\Share20\IShareProvider'); + $manager->registerProvider('foo', [\OCP\Share::SHARE_TYPE_USER], function() use ($provider) { + return $provider; + }); + $manager->registerProvider('bar', [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP], function() use ($provider) { + return $provider; + }); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage No provider with id foo found + */ + public function testGetProviderNoProviderWithId() { + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l + ); + + $this->invokePrivate($manager, 'getProvider', ['foo']); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage Callback does not return an IShareProvider instance for provider with id foo + */ + public function testGetProviderNoIShareProvider() { + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l + ); + + $provider = $this->getMock('OC\Share20\IShare'); + $manager->registerProvider('foo', [\OCP\Share::SHARE_TYPE_USER], function() use ($provider) { + return $provider; + }); + $this->invokePrivate($manager, 'getProvider', ['foo']); + } + + /** + * @expectedException \OC\Share20\Exception\ProviderException + * @expectedExceptionMessage No share provider registered for share type 1 + */ + public function testGetProviderForTypeUnkownType() { + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l + ); + + $provider = $this->getMock('OC\Share20\IShareProvider'); + $manager->registerProvider('foo', [\OCP\Share::SHARE_TYPE_USER], function() use ($provider) { + return $provider; + }); + $this->invokePrivate($manager, 'getProviderForType', [\OCP\SHARE::SHARE_TYPE_GROUP]); + } } class DummyPassword {