diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 120365263a..b54f8a69fd 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,28 @@ class FederatedShareProvider implements IShareProvider { $result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes'); return ($result === 'yes'); } + + 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_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')) + )) + ->setMaxResults(1); + $cursor = $qb->execute(); + + $remote = $cursor->fetch() !== false; + $cursor->closeCursor(); + + return ['users' => [], 'remote' => $remote, 'public' => false]; + } } diff --git a/apps/sharebymail/lib/ShareByMailProvider.php b/apps/sharebymail/lib/ShareByMailProvider.php index 0b959ce426..c367130487 100644 --- a/apps/sharebymail/lib/ShareByMailProvider.php +++ b/apps/sharebymail/lib/ShareByMailProvider.php @@ -834,4 +834,8 @@ class ShareByMailProvider implements IShareProvider { return $shares; } + public function getAccessList($nodes, $currentAccess) { + return ['users' => [], 'remote' => false, 'public' => false]; + } + } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index ab6a378114..3cf8733e41 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -794,6 +794,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 1b2c9f84df..4aecf62329 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -824,6 +824,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/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index feae147066..93e8cfc9d7 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,74 @@ 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('share_type', 'share_with') + ->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] + 1 : 1; + } 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) { + $users[$user->getUID()] = isset($users[$user->getUID()]) ? $users[$user->getUID()] + 1 : 1; + } + } else if ($type === \OCP\Share::SHARE_TYPE_LINK) { + $link = true; + } else if ($type === self::SHARE_TYPE_USERGROUP) { + if ($currentAccess === true) { + $uid = $row['share_with']; + $users[$uid] = isset($users[$uid]) ? $users[$uid] - 1 : -1; + } + } + } + $cursor->closeCursor(); + + $users = array_filter($users, function($count) { + return $count > 0; + }); + + return ['users' => array_keys($users), 'public' => $link, 'remote' => false]; + } } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index d93883e95d..096d05fbec 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -1200,69 +1200,50 @@ class Manager implements IManager { * * @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, $recursive = true) { + public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) { $owner = $path->getOwner()->getUID(); + $al = ['users' => [], 'public' => false, 'remote' => 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 IShare[] $shares */ - $shares = []; + /** @var Node[] $nodes */ + $nodes = []; + + $al['users'][] = $owner; // Collect all the shares while ($path->getPath() !== $userFolder->getPath()) { - foreach ($providers as $provider) { - $shares = array_merge($shares, $provider->getSharesByPath($path)); - } + $nodes[] = $path; if (!$recursive) { break; } $path = $path->getParent(); } - $users = [$owner => 'null']; - $public = false; - $remote = false; - foreach ($shares as $share) { - if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { - $uid = $share->getSharedWith(); + foreach ($providers as $provider) { + $tmp = $provider->getAccessList($nodes, $currentAccess); - // Skip if user does not exist - if (!$this->userManager->userExists($uid)) { - continue; - } - - $users[$uid] = null; - } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { - $group = $this->groupManager->get($share->getSharedWith()); - - // If group does not exist skip - if ($group === null) { - continue; - } - - $groupUsers = $group->getUsers(); - foreach ($groupUsers as $groupUser) { - $users[$groupUser->getUID()] = null; - } - } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { - $public = true; - } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) { - $remote = true; - } + $al['users'] = array_merge($al['users'], $tmp['users']); + $al['public'] = $al['public'] || $tmp['public']; + $al['remote'] = $al['remote'] || $tmp['remote']; } - $users = array_keys($users); + $al['users'] = array_unique($al['users']); - return ['users' => $users, 'public' => $public, 'remote' => $remote]; + return $al; } /** diff --git a/lib/public/Share/IShareProvider.php b/lib/public/Share/IShareProvider.php index 6257c97eb7..2533e81abf 100644 --- a/lib/public/Share/IShareProvider.php +++ b/lib/public/Share/IShareProvider.php @@ -190,4 +190,21 @@ interface IShareProvider { * @since 9.1.0 */ public function userDeletedFromGroup($uid, $gid); + + /** + * Get the access list to the array of provided nodes. + * Return will look like: + * + * [ + * users => ['user1', 'user2', 'user4'], + * public => bool + * remote => bool + * ] + * + * @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 string[] + * @since 12 + */ + public function getAccessList($nodes, $currentAccess); } diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php index fce5668440..ccc9f0f511 100644 --- a/tests/lib/Share20/DefaultShareProviderTest.php +++ b/tests/lib/Share20/DefaultShareProviderTest.php @@ -2434,4 +2434,179 @@ 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'); + + $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']); + $this->assertFalse($result['remote']); + + $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'); + + $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->assertContains('testShare2', $result['users']); + $this->assertContains('testShare3', $result['users']); + $this->assertContains('testShare5', $result['users']); + $this->assertTrue($result['public']); + $this->assertFalse($result['remote']); + + $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 abfe2ca9c1..ab42cb4813 100644 --- a/tests/lib/Share20/ManagerTest.php +++ b/tests/lib/Share20/ManagerTest.php @@ -2738,6 +2738,26 @@ class ManagerTest extends \Test\TestCase { } 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') @@ -2769,60 +2789,56 @@ class ManagerTest extends \Test\TestCase { $userFolder->method('getPath') ->willReturn('/owner/files'); - $userShare = $this->createMock(IShare::class); - $userShare->method('getShareType') - ->willReturn(\OCP\Share::SHARE_TYPE_USER); - $userShare->method('getSharedWith') - ->willReturn('user1'); - $groupShare = $this->createMock(IShare::class); - $groupShare->method('getShareType') - ->willReturn(\OCP\Share::SHARE_TYPE_GROUP); - $groupShare->method('getSharedWith') - ->willReturn('group1'); - $publicShare = $this->createMock(IShare::class); - $publicShare->method('getShareType') - ->willReturn(\OCP\Share::SHARE_TYPE_LINK); - $remoteShare = $this->createMock(IShare::class); - $remoteShare->method('getShareType') - ->willReturn(\OCP\Share::SHARE_TYPE_REMOTE); - $this->userManager->method('userExists') - ->with($this->equalTo('user1')) + ->with($this->equalTo('owner')) ->willReturn(true); - $user2 = $this->createMock(IUser::class); - $user2->method('getUID') - ->willReturn('user2'); - $group1 = $this->createMock(IGroup::class); - $this->groupManager->method('get') - ->with($this->equalTo('group1')) - ->willReturn($group1); - $group1->method('getUsers') - ->willReturn([$user2]); + $this->defaultProvider->method('getAccessList') + ->with( + $this->equalTo([$file, $folder]), + true + ) + ->willReturn([ + 'users' => [ + 'user1', + 'user2', + 'user3', + ], + 'remote' => false, + 'public' => true, + ]); - $this->defaultProvider->expects($this->any()) - ->method('getSharesByPath') - ->will($this->returnCallback(function(Node $path) use ($file, $folder, $userShare, $groupShare, $publicShare, $remoteShare) { - if ($path === $file) { - return [$userShare, $publicShare]; - } else if ($path === $folder) { - return [$groupShare, $remoteShare]; - } else { - return []; - } - })); + $extraProvider->method('getAccessList') + ->with( + $this->equalTo([$file, $folder]), + true + ) + ->willReturn([ + 'users' => [ + 'user3', + 'user4', + 'user5', + ], + 'remote' => true, + 'public' => false, + ]); $this->rootFolder->method('getUserFolder') ->with($this->equalTo('owner')) ->willReturn($userFolder); $expected = [ - 'users' => ['owner', 'user1', 'user2'], + 'users' => ['owner', 'user1', 'user2', 'user3', 'user4', 'user5'], 'public' => true, 'remote' => true, ]; - $this->assertEquals($expected, $this->manager->getAccessList($node)); + $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'])); + } }