diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 120365263a..34e02391e0 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -28,6 +28,7 @@ namespace OCA\FederatedFileSharing; use OC\Share20\Share; use OCP\Federation\ICloudIdManager; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\IConfig; @@ -974,4 +975,43 @@ class FederatedShareProvider implements IShareProvider { $result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes'); return ($result === 'yes'); } + + /** + * @inheritdoc + */ + public function getAccessList($nodes, $currentAccess) { + $ids = []; + foreach ($nodes as $node) { + $ids[] = $node->getId(); + } + + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('share_with', 'token', 'file_source') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE))) + ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + $cursor = $qb->execute(); + + if ($currentAccess === false) { + $remote = $cursor->fetch() !== false; + $cursor->closeCursor(); + + return ['remote' => $remote]; + } + + $remote = []; + while ($row = $cursor->fetch()) { + $remote[$row['share_with']] = [ + 'node_id' => $row['file_source'], + 'token' => $row['token'], + ]; + } + $cursor->closeCursor(); + + return ['remote' => $remote]; + } } diff --git a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php index d9bc9a7e2a..e01e02c83b 100644 --- a/apps/federatedfilesharing/tests/FederatedShareProviderTest.php +++ b/apps/federatedfilesharing/tests/FederatedShareProviderTest.php @@ -53,7 +53,7 @@ class FederatedShareProviderTest extends \Test\TestCase { protected $addressHandler; /** @var Notifications | \PHPUnit_Framework_MockObject_MockObject */ protected $notifications; - /** @var TokenHandler */ + /** @var TokenHandler|\PHPUnit_Framework_MockObject_MockObject */ protected $tokenHandler; /** @var IL10N */ protected $l; @@ -788,4 +788,60 @@ class FederatedShareProviderTest extends \Test\TestCase { $u1->delete(); $u2->delete(); } + + public function testGetAccessList() { + $userManager = \OC::$server->getUserManager(); + $rootFolder = \OC::$server->getRootFolder(); + + $u1 = $userManager->createUser('testFed', md5(time())); + + $folder1 = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo'); + $file1 = $folder1->newFile('bar1'); + + $this->tokenHandler->expects($this->exactly(2)) + ->method('generateToken') + ->willReturnOnConsecutiveCalls('token1', 'token2'); + $this->notifications->expects($this->atLeastOnce()) + ->method('sendRemoteShare') + ->willReturn(true); + + $result = $this->provider->getAccessList([$file1], true); + $this->assertEquals(['remote' => []], $result); + + $result = $this->provider->getAccessList([$file1], false); + $this->assertEquals(['remote' => false], $result); + + $share1 = $this->shareManager->newShare(); + $share1->setSharedWith('user@server.com') + ->setSharedBy($u1->getUID()) + ->setShareOwner($u1->getUID()) + ->setPermissions(\OCP\Constants::PERMISSION_READ) + ->setNode($file1); + $this->provider->create($share1); + + $share2 = $this->shareManager->newShare(); + $share2->setSharedWith('foobar@localhost') + ->setSharedBy($u1->getUID()) + ->setShareOwner($u1->getUID()) + ->setPermissions(\OCP\Constants::PERMISSION_READ) + ->setNode($file1); + $this->provider->create($share2); + + $result = $this->provider->getAccessList([$file1], true); + $this->assertEquals(['remote' => [ + 'user@server.com' => [ + 'token' => 'token1', + 'node_id' => $file1->getId(), + ], + 'foobar@localhost' => [ + 'token' => 'token2', + 'node_id' => $file1->getId(), + ], + ]], $result); + + $result = $this->provider->getAccessList([$file1], false); + $this->assertEquals(['remote' => true], $result); + + $u1->delete(); + } } diff --git a/apps/sharebymail/lib/ShareByMailProvider.php b/apps/sharebymail/lib/ShareByMailProvider.php index 0b959ce426..83170c5648 100644 --- a/apps/sharebymail/lib/ShareByMailProvider.php +++ b/apps/sharebymail/lib/ShareByMailProvider.php @@ -834,4 +834,31 @@ class ShareByMailProvider implements IShareProvider { return $shares; } + /** + * @inheritdoc + */ + public function getAccessList($nodes, $currentAccess) { + $ids = []; + foreach ($nodes as $node) { + $ids[] = $node->getId(); + } + + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('share_with') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL))) + ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )) + ->setMaxResults(1); + $cursor = $qb->execute(); + + $mail = $cursor->fetch() !== false; + $cursor->closeCursor(); + + return ['public' => $mail]; + } + } diff --git a/apps/sharebymail/tests/ShareByMailProviderTest.php b/apps/sharebymail/tests/ShareByMailProviderTest.php index 288ebb4bb4..13fb5d03bd 100644 --- a/apps/sharebymail/tests/ShareByMailProviderTest.php +++ b/apps/sharebymail/tests/ShareByMailProviderTest.php @@ -664,4 +664,66 @@ class ShareByMailProviderTest extends TestCase { $u2->delete(); } + public function testGetAccessList() { + $userManager = \OC::$server->getUserManager(); + $rootFolder = \OC::$server->getRootFolder(); + + $provider = $this->getInstance(['sendMailNotification', 'createActivity']); + + $u1 = $userManager->createUser('testFed', md5(time())); + $u2 = $userManager->createUser('testFed2', md5(time())); + + $folder = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo'); + + $accessList = $provider->getAccessList([$folder], true); + $this->assertArrayHasKey('public', $accessList); + $this->assertFalse($accessList['public']); + $accessList = $provider->getAccessList([$folder], false); + $this->assertArrayHasKey('public', $accessList); + $this->assertFalse($accessList['public']); + + $share1 = $this->shareManager->newShare(); + $share1->setSharedWith('user@server.com') + ->setSharedBy($u1->getUID()) + ->setShareOwner($u1->getUID()) + ->setPermissions(\OCP\Constants::PERMISSION_READ) + ->setNode($folder); + $share1 = $provider->create($share1); + + $share2 = $this->shareManager->newShare(); + $share2->setSharedWith('user2@server.com') + ->setSharedBy($u2->getUID()) + ->setShareOwner($u1->getUID()) + ->setPermissions(\OCP\Constants::PERMISSION_READ) + ->setNode($folder); + $share2 = $provider->create($share2); + + $accessList = $provider->getAccessList([$folder], true); + $this->assertArrayHasKey('public', $accessList); + $this->assertTrue($accessList['public']); + $accessList = $provider->getAccessList([$folder], false); + $this->assertArrayHasKey('public', $accessList); + $this->assertTrue($accessList['public']); + + $provider->delete($share2); + + $accessList = $provider->getAccessList([$folder], true); + $this->assertArrayHasKey('public', $accessList); + $this->assertTrue($accessList['public']); + $accessList = $provider->getAccessList([$folder], false); + $this->assertArrayHasKey('public', $accessList); + $this->assertTrue($accessList['public']); + + $provider->delete($share1); + + $accessList = $provider->getAccessList([$folder], true); + $this->assertArrayHasKey('public', $accessList); + $this->assertFalse($accessList['public']); + $accessList = $provider->getAccessList([$folder], false); + $this->assertArrayHasKey('public', $accessList); + $this->assertFalse($accessList['public']); + + $u1->delete(); + $u2->delete(); + } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index a978a2ec57..823a876e04 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -252,6 +252,7 @@ return array( 'OCP\\Share\\IManager' => $baseDir . '/lib/public/Share/IManager.php', 'OCP\\Share\\IProviderFactory' => $baseDir . '/lib/public/Share/IProviderFactory.php', 'OCP\\Share\\IShare' => $baseDir . '/lib/public/Share/IShare.php', + 'OCP\\Share\\IShareHelper' => $baseDir . '/lib/public/Share/IShareHelper.php', 'OCP\\Share\\IShareProvider' => $baseDir . '/lib/public/Share/IShareProvider.php', 'OCP\\Share_Backend' => $baseDir . '/lib/public/Share_Backend.php', 'OCP\\Share_Backend_Collection' => $baseDir . '/lib/public/Share_Backend_Collection.php', @@ -804,6 +805,7 @@ return array( 'OC\\Share20\\Manager' => $baseDir . '/lib/private/Share20/Manager.php', 'OC\\Share20\\ProviderFactory' => $baseDir . '/lib/private/Share20/ProviderFactory.php', 'OC\\Share20\\Share' => $baseDir . '/lib/private/Share20/Share.php', + 'OC\\Share20\\ShareHelper' => $baseDir . '/lib/private/Share20/ShareHelper.php', 'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php', 'OC\\Share\\Helper' => $baseDir . '/lib/private/Share/Helper.php', 'OC\\Share\\MailNotifications' => $baseDir . '/lib/private/Share/MailNotifications.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 0967f0541e..979679c419 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -282,6 +282,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Share\\IManager' => __DIR__ . '/../../..' . '/lib/public/Share/IManager.php', 'OCP\\Share\\IProviderFactory' => __DIR__ . '/../../..' . '/lib/public/Share/IProviderFactory.php', 'OCP\\Share\\IShare' => __DIR__ . '/../../..' . '/lib/public/Share/IShare.php', + 'OCP\\Share\\IShareHelper' => __DIR__ . '/../../..' . '/lib/public/Share/IShareHelper.php', 'OCP\\Share\\IShareProvider' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProvider.php', 'OCP\\Share_Backend' => __DIR__ . '/../../..' . '/lib/public/Share_Backend.php', 'OCP\\Share_Backend_Collection' => __DIR__ . '/../../..' . '/lib/public/Share_Backend_Collection.php', @@ -834,6 +835,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Share20\\Manager' => __DIR__ . '/../../..' . '/lib/private/Share20/Manager.php', 'OC\\Share20\\ProviderFactory' => __DIR__ . '/../../..' . '/lib/private/Share20/ProviderFactory.php', 'OC\\Share20\\Share' => __DIR__ . '/../../..' . '/lib/private/Share20/Share.php', + 'OC\\Share20\\ShareHelper' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareHelper.php', 'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php', 'OC\\Share\\Helper' => __DIR__ . '/../../..' . '/lib/private/Share/Helper.php', 'OC\\Share\\MailNotifications' => __DIR__ . '/../../..' . '/lib/private/Share/MailNotifications.php', diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php index 240a8f1cca..2bc0e014f0 100644 --- a/lib/private/Encryption/File.php +++ b/lib/private/Encryption/File.php @@ -25,12 +25,21 @@ namespace OC\Encryption; use OC\Cache\CappedMemoryCache; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Share\IManager; class File implements \OCP\Encryption\IFile { /** @var Util */ protected $util; + /** @var IRootFolder */ + private $rootFolder; + + /** @var IManager */ + private $shareManager; + /** * cache results of already checked folders * @@ -38,9 +47,13 @@ class File implements \OCP\Encryption\IFile { */ protected $cache; - public function __construct(Util $util) { + public function __construct(Util $util, + IRootFolder $rootFolder, + IManager $shareManager) { $this->util = $util; $this->cache = new CappedMemoryCache(); + $this->rootFolder = $rootFolder; + $this->shareManager = $shareManager; } @@ -63,26 +76,34 @@ class File implements \OCP\Encryption\IFile { } $ownerPath = substr($ownerPath, strlen('/files')); + $userFolder = $this->rootFolder->getUserFolder($owner); + try { + $file = $userFolder->get($ownerPath); + } catch (NotFoundException $e) { + $file = null; + } $ownerPath = $this->util->stripPartialFileExtension($ownerPath); - // first get the shares for the parent and cache the result so that we don't // need to check all parents for every file $parent = dirname($ownerPath); + $parentNode = $userFolder->get($parent); if (isset($this->cache[$parent])) { $resultForParents = $this->cache[$parent]; } else { - $resultForParents = \OCP\Share::getUsersSharingFile($parent, $owner); + $resultForParents = $this->shareManager->getAccessList($parentNode); $this->cache[$parent] = $resultForParents; } - $userIds = \array_merge($userIds, $resultForParents['users']); + $userIds = array_merge($userIds, $resultForParents['users']); $public = $resultForParents['public'] || $resultForParents['remote']; // Find out who, if anyone, is sharing the file - $resultForFile = \OCP\Share::getUsersSharingFile($ownerPath, $owner, false, false, false); - $userIds = \array_merge($userIds, $resultForFile['users']); - $public = $resultForFile['public'] || $resultForFile['remote'] || $public; + if ($file !== null) { + $resultForFile = $this->shareManager->getAccessList($file, false); + $userIds = array_merge($userIds, $resultForFile['users']); + $public = $resultForFile['public'] || $resultForFile['remote'] || $public; + } // check if it is a group mount if (\OCP\App::isEnabled("files_external")) { diff --git a/lib/private/Server.php b/lib/private/Server.php index 1ee34a57b9..62c17ced90 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -93,6 +93,7 @@ use OC\Security\CredentialsManager; use OC\Security\SecureRandom; use OC\Security\TrustedDomainHelper; use OC\Session\CryptoWrapper; +use OC\Share20\ShareHelper; use OC\Tagging\TagMapper; use OCA\Theming\ThemingDefaults; use OCP\App\IAppManager; @@ -106,6 +107,7 @@ use OCP\IServerContainer; use OCP\ITempManager; use OCP\RichObjectStrings\IValidator; use OCP\Security\IContentSecurityPolicyManager; +use OCP\Share\IShareHelper; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -173,7 +175,11 @@ class Server extends ServerContainer implements IServerContainer { $c->getGroupManager(), $c->getConfig() ); - return new Encryption\File($util); + return new Encryption\File( + $util, + $c->getRootFolder(), + $c->getShareManager() + ); }); $this->registerService('EncryptionKeyStorage', function (Server $c) { @@ -988,6 +994,12 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService(\OCP\ISession::class, function(SimpleContainer $c) { return $c->query(\OCP\IUserSession::class)->getSession(); }); + + $this->registerService(IShareHelper::class, function(Server $c) { + return new ShareHelper( + $c->query(\OCP\Share\IManager::class) + ); + }); } /** diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index feae147066..ed3651df9b 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -362,7 +362,7 @@ class DefaultShareProvider implements IShareProvider { if ($data === false) { $qb = $this->dbConn->getQueryBuilder(); - $type = $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder'; + $type = $share->getNodeType(); //Insert new share $qb->insert('share') @@ -373,8 +373,8 @@ class DefaultShareProvider implements IShareProvider { 'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()), 'parent' => $qb->createNamedParameter($share->getId()), 'item_type' => $qb->createNamedParameter($type), - 'item_source' => $qb->createNamedParameter($share->getNode()->getId()), - 'file_source' => $qb->createNamedParameter($share->getNode()->getId()), + 'item_source' => $qb->createNamedParameter($share->getNodeId()), + 'file_source' => $qb->createNamedParameter($share->getNodeId()), 'file_target' => $qb->createNamedParameter($share->getTarget()), 'permissions' => $qb->createNamedParameter(0), 'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()), @@ -1070,4 +1070,115 @@ class DefaultShareProvider implements IShareProvider { } } } + + /** + * @inheritdoc + */ + public function getAccessList($nodes, $currentAccess) { + $ids = []; + foreach ($nodes as $node) { + $ids[] = $node->getId(); + } + + $qb = $this->dbConn->getQueryBuilder(); + + $or = $qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_LINK)) + ); + + if ($currentAccess) { + $or->add($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP))); + } + + $qb->select('id', 'parent', 'share_type', 'share_with', 'file_source', 'file_target', 'permissions') + ->from('share') + ->where( + $or + ) + ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + $cursor = $qb->execute(); + + $users = []; + $link = false; + while($row = $cursor->fetch()) { + $type = (int)$row['share_type']; + if ($type === \OCP\Share::SHARE_TYPE_USER) { + $uid = $row['share_with']; + $users[$uid] = isset($users[$uid]) ? $users[$uid] : []; + $users[$uid][$row['id']] = $row; + } else if ($type === \OCP\Share::SHARE_TYPE_GROUP) { + $gid = $row['share_with']; + $group = $this->groupManager->get($gid); + + if ($gid === null) { + continue; + } + + $userList = $group->getUsers(); + foreach ($userList as $user) { + $uid = $user->getUID(); + $users[$uid] = isset($users[$uid]) ? $users[$uid] : []; + $users[$uid][$row['id']] = $row; + } + } else if ($type === \OCP\Share::SHARE_TYPE_LINK) { + $link = true; + } else if ($type === self::SHARE_TYPE_USERGROUP && $currentAccess === true) { + $uid = $row['share_with']; + $users[$uid] = isset($users[$uid]) ? $users[$uid] : []; + $users[$uid][$row['id']] = $row; + } + } + $cursor->closeCursor(); + + if ($currentAccess === true) { + $users = array_map([$this, 'filterSharesOfUser'], $users); + $users = array_filter($users); + } else { + $users = array_keys($users); + } + + return ['users' => $users, 'public' => $link]; + } + + /** + * For each user the path with the fewest slashes is returned + * @param array $shares + * @return array + */ + protected function filterSharesOfUser(array $shares) { + // Group shares when the user has a share exception + foreach ($shares as $id => $share) { + $type = (int) $share['share_type']; + $permissions = (int) $share['permissions']; + + if ($type === self::SHARE_TYPE_USERGROUP) { + unset($shares[$share['parent']]); + + if ($permissions === 0) { + unset($shares[$id]); + } + } + } + + $best = []; + $bestDepth = 0; + foreach ($shares as $id => $share) { + $depth = substr_count($share['file_target'], '/'); + if (empty($best) || $depth < $bestDepth) { + $bestDepth = $depth; + $best = [ + 'node_id' => $share['file_source'], + 'node_path' => $share['file_target'], + ]; + } + } + + return $best; + } } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 292b07d28d..6e59629153 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -48,6 +48,7 @@ use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IProviderFactory; +use OCP\Share\IShare; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\GenericEvent; use OCP\Share\IShareProvider; @@ -1176,29 +1177,109 @@ class Manager implements IManager { /** * Get access list to a path. This means - * all the users and groups that can access a given path. + * all the users that can access a given path. * * Consider: * -root - * |-folder1 - * |-folder2 - * |-fileA + * |-folder1 (23) + * |-folder2 (32) + * |-fileA (42) * - * fileA is shared with user1 - * folder2 is shared with group2 - * folder1 is shared with user2 + * fileA is shared with user1 and user1@server1 + * folder2 is shared with group2 (user4 is a member of group2) + * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2 * - * Then the access list will to '/folder1/folder2/fileA' is: + * Then the access list to '/folder1/folder2/fileA' with $currentAccess is: * [ - * 'users' => ['user1', 'user2'], - * 'groups' => ['group2'] + * users => [ + * 'user1' => ['node_id' => 42, 'node_path' => '/fileA'], + * 'user4' => ['node_id' => 32, 'node_path' => '/folder2'], + * 'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'], + * ], + * remote => [ + * 'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'], + * 'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'], + * ], + * public => bool + * mail => bool * ] * - * This is required for encryption + * The access list to '/folder1/folder2/fileA' **without** $currentAccess is: + * [ + * users => ['user1', 'user2', 'user4'], + * remote => bool, + * public => bool + * mail => bool + * ] + * + * This is required for encryption/activity * * @param \OCP\Files\Node $path + * @param bool $recursive Should we check all parent folders as well + * @param bool $currentAccess Should the user have currently access to the file + * @return array */ - public function getAccessList(\OCP\Files\Node $path) { + public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) { + $owner = $path->getOwner()->getUID(); + + if ($currentAccess) { + $al = ['users' => [], 'remote' => [], 'public' => false]; + } else { + $al = ['users' => [], 'remote' => false, 'public' => false]; + } + if (!$this->userManager->userExists($owner)) { + return $al; + } + + //Get node for the owner + $userFolder = $this->rootFolder->getUserFolder($owner); + if (!$userFolder->isSubNode($path)) { + $path = $userFolder->getById($path->getId())[0]; + } + + $providers = $this->factory->getAllProviders(); + + /** @var Node[] $nodes */ + $nodes = []; + + + if ($currentAccess) { + $ownerPath = $path->getPath(); + list(, , , $ownerPath) = explode('/', $ownerPath, 4); + $al['users'][$owner] = [ + 'node_id' => $path->getId(), + 'node_path' => '/' . $ownerPath, + ]; + } else { + $al['users'][] = $owner; + } + + // Collect all the shares + while ($path->getPath() !== $userFolder->getPath()) { + $nodes[] = $path; + if (!$recursive) { + break; + } + $path = $path->getParent(); + } + + foreach ($providers as $provider) { + $tmp = $provider->getAccessList($nodes, $currentAccess); + + foreach ($tmp as $k => $v) { + if (isset($al[$k])) { + if (is_array($al[$k])) { + $al[$k] = array_merge($al[$k], $v); + } else { + $al[$k] = $al[$k] || $v; + } + } else { + $al[$k] = $v; + } + } + } + + return $al; } /** diff --git a/lib/private/Share20/ShareHelper.php b/lib/private/Share20/ShareHelper.php new file mode 100644 index 0000000000..358b4e8026 --- /dev/null +++ b/lib/private/Share20/ShareHelper.php @@ -0,0 +1,217 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OC\Share20; + +use OCP\Files\InvalidPathException; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Share\IManager; +use OCP\Share\IShareHelper; + +class ShareHelper implements IShareHelper { + + /** @var IManager */ + private $shareManager; + + public function __construct(IManager $shareManager) { + $this->shareManager = $shareManager; + } + + /** + * @param Node $node + * @return array [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]] + */ + public function getPathsForAccessList(Node $node) { + $result = [ + 'users' => [], + 'remotes' => [], + ]; + + $accessList = $this->shareManager->getAccessList($node, true, true); + if (!empty($accessList['users'])) { + $result['users'] = $this->getPathsForUsers($node, $accessList['users']); + } + if (!empty($accessList['remote'])) { + $result['remotes'] = $this->getPathsForRemotes($node, $accessList['remote']); + } + + return $result; + } + + /** + * Sample: + * $users = [ + * 'test1' => ['node_id' => 16, 'node_path' => '/foo'], + * 'test2' => ['node_id' => 23, 'node_path' => '/bar'], + * 'test3' => ['node_id' => 42, 'node_path' => '/cat'], + * 'test4' => ['node_id' => 48, 'node_path' => '/dog'], + * ]; + * + * Node tree: + * - SixTeen is the parent of TwentyThree + * - TwentyThree is the parent of FortyTwo + * - FortyEight does not exist + * + * $return = [ + * 'test1' => '/foo/TwentyThree/FortyTwo', + * 'test2' => '/bar/FortyTwo', + * 'test3' => '/cat', + * ], + * + * @param Node $node + * @param array[] $users + * @return array + */ + protected function getPathsForUsers(Node $node, array $users) { + /** @var array[] $byId */ + $byId = []; + /** @var array[] $results */ + $results = []; + + foreach ($users as $uid => $info) { + if (!isset($byId[$info['node_id']])) { + $byId[$info['node_id']] = []; + } + $byId[$info['node_id']][$uid] = $info['node_path']; + } + + try { + if (isset($byId[$node->getId()])) { + foreach ($byId[$node->getId()] as $uid => $path) { + $results[$uid] = $path; + } + unset($byId[$node->getId()]); + } + } catch (NotFoundException $e) { + return $results; + } catch (InvalidPathException $e) { + return $results; + } + + if (empty($byId)) { + return $results; + } + + $item = $node; + $appendix = '/' . $node->getName(); + while (!empty($byId)) { + try { + /** @var Node $item */ + $item = $item->getParent(); + + if (!empty($byId[$item->getId()])) { + foreach ($byId[$item->getId()] as $uid => $path) { + $results[$uid] = $path . $appendix; + } + unset($byId[$item->getId()]); + } + + $appendix = '/' . $item->getName() . $appendix; + } catch (NotFoundException $e) { + return $results; + } catch (InvalidPathException $e) { + return $results; + } catch (NotPermittedException $e) { + return $results; + } + } + + return $results; + } + + /** + * Sample: + * $remotes = [ + * 'test1' => ['node_id' => 16, 'token' => 't1'], + * 'test2' => ['node_id' => 23, 'token' => 't2'], + * 'test3' => ['node_id' => 42, 'token' => 't3'], + * 'test4' => ['node_id' => 48, 'token' => 't4'], + * ]; + * + * Node tree: + * - SixTeen is the parent of TwentyThree + * - TwentyThree is the parent of FortyTwo + * - FortyEight does not exist + * + * $return = [ + * 'test1' => ['token' => 't1', 'node_path' => '/SixTeen'], + * 'test2' => ['token' => 't2', 'node_path' => '/SixTeen/TwentyThree'], + * 'test3' => ['token' => 't3', 'node_path' => '/SixTeen/TwentyThree/FortyTwo'], + * ], + * + * @param Node $node + * @param array[] $remotes + * @return array + */ + protected function getPathsForRemotes(Node $node, array $remotes) { + /** @var array[] $byId */ + $byId = []; + /** @var array[] $results */ + $results = []; + + foreach ($remotes as $cloudId => $info) { + if (!isset($byId[$info['node_id']])) { + $byId[$info['node_id']] = []; + } + $byId[$info['node_id']][$cloudId] = $info['token']; + } + + $item = $node; + while (!empty($byId)) { + try { + if (!empty($byId[$item->getId()])) { + $path = $this->getMountedPath($item); + foreach ($byId[$item->getId()] as $uid => $token) { + $results[$uid] = [ + 'node_path' => $path, + 'token' => $token, + ]; + } + unset($byId[$item->getId()]); + } + + /** @var Node $item */ + $item = $item->getParent(); + } catch (NotFoundException $e) { + return $results; + } catch (InvalidPathException $e) { + return $results; + } catch (NotPermittedException $e) { + return $results; + } + } + + return $results; + } + + /** + * @param Node $node + * @return string + */ + protected function getMountedPath(Node $node) { + $path = $node->getPath(); + $sections = explode('/', $path, 4); + return '/' . $sections[3]; + } +} diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index a15020bbd6..31f0480306 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -191,6 +191,53 @@ interface IManager { */ public function userDeletedFromGroup($uid, $gid); + /** + * Get access list to a path. This means + * all the users that can access a given path. + * + * Consider: + * -root + * |-folder1 (23) + * |-folder2 (32) + * |-fileA (42) + * + * fileA is shared with user1 and user1@server1 + * folder2 is shared with group2 (user4 is a member of group2) + * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2 + * + * Then the access list to '/folder1/folder2/fileA' with $currentAccess is: + * [ + * users => [ + * 'user1' => ['node_id' => 42, 'node_path' => '/fileA'], + * 'user4' => ['node_id' => 32, 'node_path' => '/folder2'], + * 'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'], + * ], + * remote => [ + * 'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'], + * 'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'], + * ], + * public => bool + * mail => bool + * ] + * + * The access list to '/folder1/folder2/fileA' **without** $currentAccess is: + * [ + * users => ['user1', 'user2', 'user4'], + * remote => bool, + * public => bool + * mail => bool + * ] + * + * This is required for encryption/activity + * + * @param \OCP\Files\Node $path + * @param bool $recursive Should we check all parent folders as well + * @param bool $currentAccess Should the user have currently access to the file + * @return array + * @since 12 + */ + public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false); + /** * Instantiates a new share object. This is to be passed to * createShare. diff --git a/lib/public/Share/IShareHelper.php b/lib/public/Share/IShareHelper.php new file mode 100644 index 0000000000..4ec62830c5 --- /dev/null +++ b/lib/public/Share/IShareHelper.php @@ -0,0 +1,41 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCP\Share; + +use OCP\Files\Node; + +/** + * Interface IShareHelper + * + * @package OCP\Share + * @since 12 + */ +interface IShareHelper { + + /** + * @param Node $node + * @return array [ users => [Mapping $uid => $pathForUser], remotes => [Mapping $cloudId => $pathToMountRoot]] + * @since 12 + */ + public function getPathsForAccessList(Node $node); +} diff --git a/lib/public/Share/IShareProvider.php b/lib/public/Share/IShareProvider.php index 6257c97eb7..31808206cf 100644 --- a/lib/public/Share/IShareProvider.php +++ b/lib/public/Share/IShareProvider.php @@ -190,4 +190,16 @@ interface IShareProvider { * @since 9.1.0 */ public function userDeletedFromGroup($uid, $gid); + + /** + * Get the access list to the array of provided nodes. + * + * @see IManager::getAccessList() for sample docs + * + * @param Node[] $nodes The list of nodes to get access for + * @param bool $currentAccess If current access is required (like for removed shares that might get revived later) + * @return array + * @since 12 + */ + public function getAccessList($nodes, $currentAccess); } diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php index fce5668440..0fa8aa3d0c 100644 --- a/tests/lib/Share20/DefaultShareProviderTest.php +++ b/tests/lib/Share20/DefaultShareProviderTest.php @@ -1545,9 +1545,9 @@ class DefaultShareProviderTest extends \Test\TestCase { $this->assertEquals(1, $stmt); $id = $qb->getLastInsertId(); - $user1 = $this->getMock('\OCP\IUser'); + $user1 = $this->createMock(IUser::class); $user1->method('getUID')->willReturn('user1'); - $user2 = $this->getMock('\OCP\IUser'); + $user2 = $this->createMock(IUser::class); $user2->method('getUID')->willReturn('user2'); $this->userManager->method('get')->will($this->returnValueMap([ ['user1', $user1], @@ -1556,7 +1556,7 @@ class DefaultShareProviderTest extends \Test\TestCase { $this->groupManager->method('get')->with('group')->willReturn(null); - $file = $this->getMock('\OCP\Files\File'); + $file = $this->createMock(File::class); $file->method('getId')->willReturn(1); $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); @@ -2434,4 +2434,185 @@ class DefaultShareProviderTest extends \Test\TestCase { $u3->delete(); $g1->delete(); } + + public function testGetAccessListNoCurrentAccessRequired() { + $userManager = \OC::$server->getUserManager(); + $groupManager = \OC::$server->getGroupManager(); + $rootFolder = \OC::$server->getRootFolder(); + + $provider = new DefaultShareProvider( + $this->dbConn, + $userManager, + $groupManager, + $rootFolder + ); + + $u1 = $userManager->createUser('testShare1', 'test'); + $u2 = $userManager->createUser('testShare2', 'test'); + $u3 = $userManager->createUser('testShare3', 'test'); + $u4 = $userManager->createUser('testShare4', 'test'); + $u5 = $userManager->createUser('testShare5', 'test'); + + $g1 = $groupManager->createGroup('group1'); + $g1->addUser($u3); + $g1->addUser($u4); + + $u1Folder = $rootFolder->getUserFolder($u1->getUID()); + $folder1 = $u1Folder->newFolder('foo'); + $folder2 = $folder1->newFolder('baz'); + $file1 = $folder2->newFile('bar'); + + $result = $provider->getAccessList([$folder1, $folder2, $file1], false); + $this->assertCount(0, $result['users']); + $this->assertFalse($result['public']); + + $shareManager = \OC::$server->getShareManager(); + $share1 = $shareManager->newShare(); + $share1->setNode($folder1) + ->setSharedBy($u1->getUID()) + ->setSharedWith($u2->getUID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_USER) + ->setPermissions(\OCP\Constants::PERMISSION_ALL); + $share1 = $this->provider->create($share1); + + $share2 = $shareManager->newShare(); + $share2->setNode($folder2) + ->setSharedBy($u2->getUID()) + ->setSharedWith($g1->getGID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_GROUP) + ->setPermissions(\OCP\Constants::PERMISSION_ALL); + $share2 = $this->provider->create($share2); + + $shareManager->deleteFromSelf($share2, $u4->getUID()); + + $share3 = $shareManager->newShare(); + $share3->setNode($file1) + ->setSharedBy($u3->getUID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPermissions(\OCP\Constants::PERMISSION_READ); + $share3 = $this->provider->create($share3); + + $share4 = $shareManager->newShare(); + $share4->setNode($file1) + ->setSharedBy($u3->getUID()) + ->setSharedWith($u5->getUID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_USER) + ->setPermissions(\OCP\Constants::PERMISSION_READ); + $share4 = $this->provider->create($share4); + + $result = $provider->getAccessList([$folder1, $folder2, $file1], false); + + $this->assertCount(4, $result['users']); + $this->assertContains('testShare2', $result['users']); + $this->assertContains('testShare3', $result['users']); + $this->assertContains('testShare4', $result['users']); + $this->assertContains('testShare5', $result['users']); + $this->assertTrue($result['public']); + + $provider->delete($share1); + $provider->delete($share2); + $provider->delete($share3); + $provider->delete($share4); + + $u1->delete(); + $u2->delete(); + $u3->delete(); + $u4->delete(); + $u5->delete(); + $g1->delete(); + } + + public function testGetAccessListCurrentAccessRequired() { + $userManager = \OC::$server->getUserManager(); + $groupManager = \OC::$server->getGroupManager(); + $rootFolder = \OC::$server->getRootFolder(); + + $provider = new DefaultShareProvider( + $this->dbConn, + $userManager, + $groupManager, + $rootFolder + ); + + $u1 = $userManager->createUser('testShare1', 'test'); + $u2 = $userManager->createUser('testShare2', 'test'); + $u3 = $userManager->createUser('testShare3', 'test'); + $u4 = $userManager->createUser('testShare4', 'test'); + $u5 = $userManager->createUser('testShare5', 'test'); + + $g1 = $groupManager->createGroup('group1'); + $g1->addUser($u3); + $g1->addUser($u4); + + $u1Folder = $rootFolder->getUserFolder($u1->getUID()); + $folder1 = $u1Folder->newFolder('foo'); + $folder2 = $folder1->newFolder('baz'); + $file1 = $folder2->newFile('bar'); + + $result = $provider->getAccessList([$folder1, $folder2, $file1], false); + $this->assertCount(0, $result['users']); + $this->assertFalse($result['public']); + + $shareManager = \OC::$server->getShareManager(); + $share1 = $shareManager->newShare(); + $share1->setNode($folder1) + ->setSharedBy($u1->getUID()) + ->setSharedWith($u2->getUID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_USER) + ->setPermissions(\OCP\Constants::PERMISSION_ALL); + $share1 = $this->provider->create($share1); + + $share2 = $shareManager->newShare(); + $share2->setNode($folder2) + ->setSharedBy($u2->getUID()) + ->setSharedWith($g1->getGID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_GROUP) + ->setPermissions(\OCP\Constants::PERMISSION_ALL); + $share2 = $this->provider->create($share2); + + $shareManager->deleteFromSelf($share2, $u4->getUID()); + + $share3 = $shareManager->newShare(); + $share3->setNode($file1) + ->setSharedBy($u3->getUID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPermissions(\OCP\Constants::PERMISSION_READ); + $share3 = $this->provider->create($share3); + + $share4 = $shareManager->newShare(); + $share4->setNode($file1) + ->setSharedBy($u3->getUID()) + ->setSharedWith($u5->getUID()) + ->setShareOwner($u1->getUID()) + ->setShareType(\OCP\Share::SHARE_TYPE_USER) + ->setPermissions(\OCP\Constants::PERMISSION_READ); + $share4 = $this->provider->create($share4); + + $result = $provider->getAccessList([$folder1, $folder2, $file1], true); + + $this->assertCount(3, $result['users']); + $this->assertArrayHasKey('testShare2', $result['users']); + $this->assertArrayHasKey('testShare3', $result['users']); + $this->assertArrayHasKey('testShare5', $result['users']); + $this->assertTrue($result['public']); + + $provider->delete($share1); + $provider->delete($share2); + $provider->delete($share3); + $provider->delete($share4); + + $u1->delete(); + $u2->delete(); + $u3->delete(); + $u4->delete(); + $u5->delete(); + $g1->delete(); + } } diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php index 5436b18835..42308a9d6a 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -1022,12 +1022,12 @@ class ManagerTest extends \Test\TestCase { public function testUserCreateChecksIdenticalPathSharedViaDeletedGroup() { $share = $this->manager->newShare(); - $sharedWith = $this->getMock('\OCP\IUser'); + $sharedWith = $this->createMock(IUser::class); $sharedWith->method('getUID')->willReturn('sharedWith'); $this->userManager->method('get')->with('sharedWith')->willReturn($sharedWith); - $path = $this->getMock('\OCP\Files\Node'); + $path = $this->createMock(Node::class); $share->setSharedWith('sharedWith') ->setNode($path) @@ -1136,7 +1136,7 @@ class ManagerTest extends \Test\TestCase { public function testGroupCreateChecksShareWithGroupMembersOnlyNullGroup() { $share = $this->manager->newShare(); - $user = $this->getMock('\OCP\IUser'); + $user = $this->createMock(IUser::class); $share->setSharedBy('user')->setSharedWith('group'); $this->groupManager->method('get')->with('group')->willReturn(null); @@ -2611,7 +2611,7 @@ class ManagerTest extends \Test\TestCase { $share->setShareType(\OCP\Share::SHARE_TYPE_GROUP); $share->setSharedWith('shareWith'); - $recipient = $this->getMock('\OCP\IUser'); + $recipient = $this->createMock(IUser::class); $this->groupManager->method('get')->with('shareWith')->willReturn(null); $this->userManager->method('get')->with('recipient')->willReturn($recipient); @@ -2639,7 +2639,6 @@ class ManagerTest extends \Test\TestCase { $this->manager->moveShare($share, 'recipient'); } - /** * @dataProvider dataTestShareProviderExists */ @@ -2737,6 +2736,119 @@ class ManagerTest extends \Test\TestCase { $this->assertSame($expects, $result); } + + public function testGetAccessList() { + $factory = new DummyFactory2($this->createMock(IServerContainer::class)); + + $manager = new Manager( + $this->logger, + $this->config, + $this->secureRandom, + $this->hasher, + $this->mountManager, + $this->groupManager, + $this->l, + $factory, + $this->userManager, + $this->rootFolder, + $this->eventDispatcher + ); + + $factory->setProvider($this->defaultProvider); + $extraProvider = $this->createMock(IShareProvider::class); + $factory->setSecondProvider($extraProvider); + + $owner = $this->createMock(IUser::class); + $owner->expects($this->once()) + ->method('getUID') + ->willReturn('owner'); + + $node = $this->createMock(Node::class); + $node->expects($this->once()) + ->method('getOwner') + ->willReturn($owner); + $node->expects($this->once()) + ->method('getId') + ->willReturn(42); + + $userFolder = $this->createMock(Folder::class); + $file = $this->createMock(File::class); + $folder = $this->createMock(Folder::class); + + $file->method('getParent') + ->willReturn($folder); + $file->method('getPath') + ->willReturn('/owner/files/folder/file'); + $file->method('getId') + ->willReturn(23); + $folder->method('getParent') + ->willReturn($userFolder); + $folder->method('getPath') + ->willReturn('/owner/files/folder'); + $userFolder->method('getById') + ->with($this->equalTo(42)) + ->willReturn([$file]); + $userFolder->method('getPath') + ->willReturn('/owner/files'); + + $this->userManager->method('userExists') + ->with($this->equalTo('owner')) + ->willReturn(true); + + $this->defaultProvider->method('getAccessList') + ->with( + $this->equalTo([$file, $folder]), + true + ) + ->willReturn([ + 'users' => [ + 'user1' => [], + 'user2' => [], + 'user3' => [], + ], + 'public' => true, + ]); + + $extraProvider->method('getAccessList') + ->with( + $this->equalTo([$file, $folder]), + true + ) + ->willReturn([ + 'users' => [ + 'user3' => [], + 'user4' => [], + 'user5' => [], + ], + 'remote' => [ + 'remote1', + ], + ]); + + $this->rootFolder->method('getUserFolder') + ->with($this->equalTo('owner')) + ->willReturn($userFolder); + + $expected = [ + 'users' => [ + 'owner' => [ + 'node_id' => 23, + 'node_path' => '/folder/file' + ] + , 'user1' => [], 'user2' => [], 'user3' => [], 'user4' => [], 'user5' => []], + 'remote' => [ + 'remote1', + ], + 'public' => true, + ]; + + $result = $manager->getAccessList($node, true, true); + + $this->assertSame($expected['public'], $result['public']); + $this->assertSame($expected['remote'], $result['remote']); + $this->assertSame(array_values($expected['users']), array_values($result['users'])); + + } } class DummyFactory implements IProviderFactory { diff --git a/tests/lib/Share20/ShareHelperTest.php b/tests/lib/Share20/ShareHelperTest.php new file mode 100644 index 0000000000..69609f9f72 --- /dev/null +++ b/tests/lib/Share20/ShareHelperTest.php @@ -0,0 +1,231 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace Test\Share20; + +use OC\Share20\ShareHelper; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Share\IManager; +use Test\TestCase; + +class ShareHelperTest extends TestCase { + + /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */ + private $manager; + + /** @var ShareHelper */ + private $helper; + + public function setUp() { + parent::setUp(); + + $this->manager = $this->createMock(IManager::class); + + $this->helper = new ShareHelper($this->manager); + } + + public function dataGetPathsForAccessList() { + return [ + [[], [], false, [], [], false, [ + 'users' => [], + 'remotes' => [], + ]], + [['user1', 'user2'], ['user1' => 'foo', 'user2' => 'bar'], true, [], [], false, [ + 'users' => ['user1' => 'foo', 'user2' => 'bar'], + 'remotes' => [], + ]], + [[], [], false, ['remote1', 'remote2'], ['remote1' => 'qwe', 'remote2' => 'rtz'], true, [ + 'users' => [], + 'remotes' => ['remote1' => 'qwe', 'remote2' => 'rtz'], + ]], + [['user1', 'user2'], ['user1' => 'foo', 'user2' => 'bar'], true, ['remote1', 'remote2'], ['remote1' => 'qwe', 'remote2' => 'rtz'], true, [ + 'users' => ['user1' => 'foo', 'user2' => 'bar'], + 'remotes' => ['remote1' => 'qwe', 'remote2' => 'rtz'], + ]], + ]; + } + + /** + * @dataProvider dataGetPathsForAccessList + */ + public function testGetPathsForAccessList(array $userList, array $userMap, $resolveUsers, array $remoteList, array $remoteMap, $resolveRemotes, array $expected) { + $this->manager->expects($this->once()) + ->method('getAccessList') + ->willReturn([ + 'users' => $userList, + 'remote' => $remoteList, + ]); + + /** @var Node|\PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->createMock(Node::class); + /** @var ShareHelper|\PHPUnit_Framework_MockObject_MockObject $helper */ + $helper = $this->getMockBuilder(ShareHelper::class) + ->setConstructorArgs([$this->manager]) + ->setMethods(['getPathsForUsers', 'getPathsForRemotes']) + ->getMock(); + + $helper->expects($resolveUsers ? $this->once() : $this->never()) + ->method('getPathsForUsers') + ->with($node, $userList) + ->willReturn($userMap); + + $helper->expects($resolveRemotes ? $this->once() : $this->never()) + ->method('getPathsForRemotes') + ->with($node, $remoteList) + ->willReturn($remoteMap); + + $this->assertSame($expected, $helper->getPathsForAccessList($node)); + } + + public function dataGetPathsForUsers() { + return [ + [[], [23 => 'TwentyThree', 42 => 'FortyTwo'], []], + [ + [ + 'test1' => ['node_id' => 16, 'node_path' => '/foo'], + 'test2' => ['node_id' => 23, 'node_path' => '/bar'], + 'test3' => ['node_id' => 42, 'node_path' => '/cat'], + 'test4' => ['node_id' => 48, 'node_path' => '/dog'], + ], + [16 => 'SixTeen', 23 => 'TwentyThree', 42 => 'FortyTwo'], + [ + 'test1' => '/foo/TwentyThree/FortyTwo', + 'test2' => '/bar/FortyTwo', + 'test3' => '/cat', + ], + ], + ]; + } + + /** + * @dataProvider dataGetPathsForUsers + * + * @param array $users + * @param array $nodes + * @param array $expected + */ + public function testGetPathsForUsers(array $users, array $nodes, array $expected) { + $lastNode = null; + foreach ($nodes as $nodeId => $nodeName) { + /** @var Node|\PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->createMock(Node::class); + $node->expects($this->any()) + ->method('getId') + ->willReturn($nodeId); + $node->expects($this->any()) + ->method('getName') + ->willReturn($nodeName); + if ($lastNode === null) { + $node->expects($this->any()) + ->method('getParent') + ->willThrowException(new NotFoundException()); + } else { + $node->expects($this->any()) + ->method('getParent') + ->willReturn($lastNode); + } + $lastNode = $node; + } + + $this->assertEquals($expected, self::invokePrivate($this->helper, 'getPathsForUsers', [$lastNode, $users])); + } + + public function dataGetPathsForRemotes() { + return [ + [[], [23 => 'TwentyThree', 42 => 'FortyTwo'], []], + [ + [ + 'test1' => ['node_id' => 16, 'token' => 't1'], + 'test2' => ['node_id' => 23, 'token' => 't2'], + 'test3' => ['node_id' => 42, 'token' => 't3'], + 'test4' => ['node_id' => 48, 'token' => 't4'], + ], + [ + 16 => '/admin/files/SixTeen', + 23 => '/admin/files/SixTeen/TwentyThree', + 42 => '/admin/files/SixTeen/TwentyThree/FortyTwo', + ], + [ + 'test1' => ['token' => 't1', 'node_path' => '/SixTeen'], + 'test2' => ['token' => 't2', 'node_path' => '/SixTeen/TwentyThree'], + 'test3' => ['token' => 't3', 'node_path' => '/SixTeen/TwentyThree/FortyTwo'], + ], + ], + ]; + } + + /** + * @dataProvider dataGetPathsForRemotes + * + * @param array $remotes + * @param array $nodes + * @param array $expected + */ + public function testGetPathsForRemotes(array $remotes, array $nodes, array $expected) { + $lastNode = null; + foreach ($nodes as $nodeId => $nodePath) { + /** @var Node|\PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->createMock(Node::class); + $node->expects($this->any()) + ->method('getId') + ->willReturn($nodeId); + $node->expects($this->any()) + ->method('getPath') + ->willReturn($nodePath); + if ($lastNode === null) { + $node->expects($this->any()) + ->method('getParent') + ->willThrowException(new NotFoundException()); + } else { + $node->expects($this->any()) + ->method('getParent') + ->willReturn($lastNode); + } + $lastNode = $node; + } + + $this->assertEquals($expected, self::invokePrivate($this->helper, 'getPathsForRemotes', [$lastNode, $remotes])); + } + + public function dataGetMountedPath() { + return [ + ['/admin/files/foobar', '/foobar'], + ['/admin/files/foo/bar', '/foo/bar'], + ]; + } + + /** + * @dataProvider dataGetMountedPath + * @param string $path + * @param string $expected + */ + public function testGetMountedPath($path, $expected) { + /** @var Node|\PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->createMock(Node::class); + $node->expects($this->once()) + ->method('getPath') + ->willReturn($path); + + $this->assertSame($expected, self::invokePrivate($this->helper, 'getMountedPath', [$node])); + } +}