diff --git a/apps/files_sharing/api/sharees.php b/apps/files_sharing/api/sharees.php new file mode 100644 index 0000000000..d7eabb9a55 --- /dev/null +++ b/apps/files_sharing/api/sharees.php @@ -0,0 +1,409 @@ + + * + * @copyright Copyright (c) 2015, 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 OCA\Files_Sharing\API; + +use OCP\Contacts\IManager; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\ILogger; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IConfig; +use OCP\IUserSession; +use OCP\IURLGenerator; +use OCP\Share; + +class Sharees { + + /** @var IGroupManager */ + protected $groupManager; + + /** @var IUserManager */ + protected $userManager; + + /** @var IManager */ + protected $contactsManager; + + /** @var IConfig */ + protected $config; + + /** @var IUserSession */ + protected $userSession; + + /** @var IRequest */ + protected $request; + + /** @var IURLGenerator */ + protected $urlGenerator; + + /** @var ILogger */ + protected $logger; + + /** @var bool */ + protected $shareWithGroupOnly = false; + + /** @var int */ + protected $offset = 0; + + /** @var int */ + protected $limit = 10; + + /** @var array */ + protected $result = [ + 'exact' => [ + 'users' => [], + 'groups' => [], + 'remotes' => [], + ], + 'users' => [], + 'groups' => [], + 'remotes' => [], + ]; + + protected $reachedEndFor = []; + + /** + * @param IGroupManager $groupManager + * @param IUserManager $userManager + * @param IManager $contactsManager + * @param IConfig $config + * @param IUserSession $userSession + * @param IURLGenerator $urlGenerator + * @param IRequest $request + * @param ILogger $logger + */ + public function __construct(IGroupManager $groupManager, + IUserManager $userManager, + IManager $contactsManager, + IConfig $config, + IUserSession $userSession, + IURLGenerator $urlGenerator, + IRequest $request, + ILogger $logger) { + $this->groupManager = $groupManager; + $this->userManager = $userManager; + $this->contactsManager = $contactsManager; + $this->config = $config; + $this->userSession = $userSession; + $this->urlGenerator = $urlGenerator; + $this->request = $request; + $this->logger = $logger; + } + + /** + * @param string $search + */ + protected function getUsers($search) { + $this->result['users'] = $this->result['exact']['users'] = $users = []; + + if ($this->shareWithGroupOnly) { + // Search in all the groups this user is part of + $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); + foreach ($userGroups as $userGroup) { + $usersTmp = $this->groupManager->displayNamesInGroup($userGroup, $search, $this->limit, $this->offset); + foreach ($usersTmp as $uid => $userDisplayName) { + $users[$uid] = $userDisplayName; + } + } + } else { + // Search in all users + $usersTmp = $this->userManager->searchDisplayName($search, $this->limit, $this->offset); + + foreach ($usersTmp as $user) { + $users[$user->getUID()] = $user->getDisplayName(); + } + } + + if (sizeof($users) < $this->limit) { + $this->reachedEndFor[] = 'users'; + } + + $foundUserById = false; + foreach ($users as $uid => $userDisplayName) { + if (strtolower($uid) === $search || strtolower($userDisplayName) === $search) { + if (strtolower($uid) === $search) { + $foundUserById = true; + } + $this->result['exact']['users'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $uid, + ], + ]; + } else { + $this->result['users'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $uid, + ], + ]; + } + } + + if ($this->offset === 0 && !$foundUserById) { + // On page one we try if the search result has a direct hit on the + // user id and if so, we add that to the exact match list + $user = $this->userManager->get($search); + if ($user instanceof IUser) { + array_push($this->result['exact']['users'], [ + 'label' => $user->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $user->getUID(), + ], + ]); + } + } + } + + /** + * @param string $search + */ + protected function getGroups($search) { + $this->result['groups'] = $this->result['exact']['groups'] = []; + + $groups = $this->groupManager->search($search, $this->limit, $this->offset); + $groups = array_map(function (IGroup $group) { return $group->getGID(); }, $groups); + + if (sizeof($groups) < $this->limit) { + $this->reachedEndFor[] = 'groups'; + } + + $userGroups = []; + if (!empty($groups) && $this->shareWithGroupOnly) { + // Intersect all the groups that match with the groups this user is a member of + $userGroups = $this->groupManager->getUserGroups($this->userSession->getUser()); + $userGroups = array_map(function (IGroup $group) { return $group->getGID(); }, $userGroups); + $groups = array_intersect($groups, $userGroups); + } + + foreach ($groups as $gid) { + if (strtolower($gid) === $search) { + $this->result['exact']['groups'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $search, + ], + ]; + } else { + $this->result['groups'][] = [ + 'label' => $gid, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $gid, + ], + ]; + } + } + + if ($this->offset === 0 && empty($this->result['exact']['groups'])) { + // On page one we try if the search result has a direct hit on the + // user id and if so, we add that to the exact match list + $group = $this->groupManager->get($search); + if ($group instanceof IGroup && (!$this->shareWithGroupOnly || in_array($group->getGID(), $userGroups))) { + array_push($this->result['exact']['groups'], [ + 'label' => $group->getGID(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $group->getGID(), + ], + ]); + } + } + } + + /** + * @param string $search + * @return array possible sharees + */ + protected function getRemote($search) { + $this->result['remotes'] = []; + + // Search in contacts + //@todo Pagination missing + $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']); + $foundRemoteById = false; + foreach ($addressBookContacts as $contact) { + if (isset($contact['CLOUD'])) { + foreach ($contact['CLOUD'] as $cloudId) { + if (strtolower($contact['FN']) === $search || strtolower($cloudId) === $search) { + if (strtolower($cloudId) === $search) { + $foundRemoteById = true; + } + $this->result['exact']['remotes'][] = [ + 'label' => $contact['FN'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + ], + ]; + } else { + $this->result['remotes'][] = [ + 'label' => $contact['FN'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + ], + ]; + } + } + } + } + + if (!$foundRemoteById && substr_count($search, '@') >= 1 && substr_count($search, ' ') === 0 && $this->offset === 0) { + $this->result['exact']['remotes'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $search, + ], + ]; + } + + $this->reachedEndFor[] = 'remotes'; + } + + /** + * @return \OC_OCS_Result + */ + public function search() { + $search = isset($_GET['search']) ? (string) $_GET['search'] : ''; + $itemType = isset($_GET['itemType']) ? (string) $_GET['itemType'] : null; + $page = !empty($_GET['page']) ? max(1, (int) $_GET['page']) : 1; + $perPage = !empty($_GET['limit']) ? max(1, (int) $_GET['limit']) : 200; + + $shareTypes = [ + Share::SHARE_TYPE_USER, + Share::SHARE_TYPE_GROUP, + Share::SHARE_TYPE_REMOTE, + ]; + if (isset($_GET['shareType']) && is_array($_GET['shareType'])) { + $shareTypes = array_intersect($shareTypes, $_GET['shareType']); + sort($shareTypes); + + } else if (isset($_GET['shareType']) && is_numeric($_GET['shareType'])) { + $shareTypes = array_intersect($shareTypes, [(int) $_GET['shareType']]); + sort($shareTypes); + } + + if (in_array(Share::SHARE_TYPE_REMOTE, $shareTypes) && !$this->isRemoteSharingAllowed($itemType)) { + // Remove remote shares from type array, because it is not allowed. + $shareTypes = array_diff($shareTypes, [Share::SHARE_TYPE_REMOTE]); + } + + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + $this->limit = (int) $perPage; + $this->offset = $perPage * ($page - 1); + + return $this->searchSharees(strtolower($search), $itemType, $shareTypes, $page, $perPage); + } + + /** + * Method to get out the static call for better testing + * + * @param string $itemType + * @return bool + */ + protected function isRemoteSharingAllowed($itemType) { + try { + $backend = Share::getBackend($itemType); + return $backend->isShareTypeAllowed(Share::SHARE_TYPE_REMOTE); + } catch (\Exception $e) { + return false; + } + } + + /** + * Testable search function that does not need globals + * + * @param string $search + * @param string $itemType + * @param array $shareTypes + * @param int $page + * @param int $perPage + * @return \OC_OCS_Result + */ + protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage) { + // Verify arguments + if ($itemType === null) { + return new \OC_OCS_Result(null, 400, 'missing itemType'); + } + + // Get users + if (in_array(Share::SHARE_TYPE_USER, $shareTypes)) { + $this->getUsers($search); + } + + // Get groups + if (in_array(Share::SHARE_TYPE_GROUP, $shareTypes)) { + $this->getGroups($search); + } + + // Get remote + if (in_array(Share::SHARE_TYPE_REMOTE, $shareTypes)) { + $this->getRemote($search); + } + + $response = new \OC_OCS_Result($this->result); + $response->setItemsPerPage($perPage); + + if (sizeof($this->reachedEndFor) < 3) { + $response->addHeader('Link', $this->getPaginationLink($page, [ + 'search' => $search, + 'itemType' => $itemType, + 'shareType' => $shareTypes, + 'limit' => $perPage, + ])); + } + + return $response; + } + + /** + * Generates a bunch of pagination links for the current page + * + * @param int $page Current page + * @param array $params Parameters for the URL + * @return string + */ + protected function getPaginationLink($page, array $params) { + if ($this->isV2()) { + $url = $this->urlGenerator->getAbsoluteURL('/ocs/v2.php/apps/files_sharing/api/v1/sharees') . '?'; + } else { + $url = $this->urlGenerator->getAbsoluteURL('/ocs/v1.php/apps/files_sharing/api/v1/sharees') . '?'; + } + $params['page'] = $page + 1; + $link = '<' . $url . http_build_query($params) . '>; rel="next"'; + + return $link; + } + + /** + * @return bool + */ + protected function isV2() { + return $this->request->getScriptName() === '/ocs/v2.php'; + } +} diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index 184ad29bba..375124cb73 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -102,3 +102,16 @@ API::register('delete', array('\OCA\Files_Sharing\API\Remote', 'declineShare'), 'files_sharing'); +$sharees = new \OCA\Files_Sharing\API\Sharees(\OC::$server->getGroupManager(), + \OC::$server->getUserManager(), + \OC::$server->getContactsManager(), + \OC::$server->getConfig(), + \OC::$server->getUserSession(), + \OC::$server->getURLGenerator(), + \OC::$server->getRequest(), + \OC::$server->getLogger()); + +API::register('get', + '/apps/files_sharing/api/v1/sharees', + [$sharees, 'search'], + 'files_sharing', API::USER_AUTH); diff --git a/apps/files_sharing/tests/api/shareestest.php b/apps/files_sharing/tests/api/shareestest.php new file mode 100644 index 0000000000..1e4438acd8 --- /dev/null +++ b/apps/files_sharing/tests/api/shareestest.php @@ -0,0 +1,995 @@ + + * + * @copyright Copyright (c) 2015, 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 OCA\Files_Sharing\Tests\API; + +use Doctrine\DBAL\Connection; +use OC\Share\Constants; +use OCA\Files_Sharing\API\Sharees; +use OCA\Files_sharing\Tests\TestCase; +use OCP\Share; + +class ShareesTest extends TestCase { + /** @var Sharees */ + protected $sharees; + + /** @var \OCP\IUserManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $userManager; + + /** @var \OCP\IGroupManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $groupManager; + + /** @var \OCP\Contacts\IManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $contactsManager; + + /** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + protected $session; + + /** @var \OCP\IRequest|\PHPUnit_Framework_MockObject_MockObject */ + protected $request; + + protected function setUp() { + parent::setUp(); + + $this->userManager = $this->getMockBuilder('OCP\IUserManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->groupManager = $this->getMockBuilder('OCP\IGroupManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->contactsManager = $this->getMockBuilder('OCP\Contacts\IManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->session = $this->getMockBuilder('OCP\IUserSession') + ->disableOriginalConstructor() + ->getMock(); + + $this->request = $this->getMockBuilder('OCP\IRequest') + ->disableOriginalConstructor() + ->getMock(); + + $this->sharees = new Sharees( + $this->groupManager, + $this->userManager, + $this->contactsManager, + $this->getMockBuilder('OCP\IConfig')->disableOriginalConstructor()->getMock(), + $this->session, + $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), + $this->request, + $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock() + ); + } + + protected function getUserMock($uid, $displayName) { + $user = $this->getMockBuilder('OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + + $user->expects($this->any()) + ->method('getUID') + ->willReturn($uid); + + $user->expects($this->any()) + ->method('getDisplayName') + ->willReturn($displayName); + + return $user; + } + + protected function getGroupMock($gid) { + $group = $this->getMockBuilder('OCP\IGroup') + ->disableOriginalConstructor() + ->getMock(); + + $group->expects($this->any()) + ->method('getGID') + ->willReturn($gid); + + return $group; + } + + public function dataGetUsers() { + return [ + ['test', false, [], [], [], [], true, false], + ['test', true, [], [], [], [], true, false], + [ + 'test', false, [], [], + [ + ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], + ], [], true, $this->getUserMock('test', 'Test') + ], + [ + 'test', true, [], [], + [ + ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], + ], [], true, $this->getUserMock('test', 'Test') + ], + [ + 'test', + false, + [], + [ + $this->getUserMock('test1', 'Test One'), + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], + true, + false, + ], + [ + 'test', + false, + [], + [ + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], + ], + false, + false, + ], + [ + 'test', + false, + [], + [ + $this->getUserMock('test0', 'Test'), + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [ + ['label' => 'Test', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test0']], + ], + [ + ['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], + ], + false, + false, + ], + [ + 'test', + true, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, ['test1' => 'Test One']], + ['xyz', 'test', 2, 0, []], + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], + true, + false, + ], + [ + 'test', + true, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ['xyz', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], + ], + false, + false, + ], + [ + 'test', + true, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test' => 'Test One', + ]], + ['xyz', 'test', 2, 0, [ + 'test2' => 'Test Two', + ]], + ], + [ + ['label' => 'Test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test']], + ], + [ + ['label' => 'Test Two', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], + ], + false, + false, + ], + ]; + } + + /** + * @dataProvider dataGetUsers + * + * @param string $searchTerm + * @param bool $shareWithGroupOnly + * @param array $groupResponse + * @param array $userResponse + * @param array $exactExpected + * @param array $expected + * @param bool $reachedEnd + * @param mixed $singleUser + */ + public function testGetUsers($searchTerm, $shareWithGroupOnly, $groupResponse, $userResponse, $exactExpected, $expected, $reachedEnd, $singleUser) { + $this->invokePrivate($this->sharees, 'limit', [2]); + $this->invokePrivate($this->sharees, 'offset', [0]); + $this->invokePrivate($this->sharees, 'shareWithGroupOnly', [$shareWithGroupOnly]); + + $user = $this->getUserMock('admin', 'Administrator'); + $this->session->expects($this->any()) + ->method('getUser') + ->willReturn($user); + + if (!$shareWithGroupOnly) { + $this->userManager->expects($this->once()) + ->method('searchDisplayName') + ->with($searchTerm, $this->invokePrivate($this->sharees, 'limit'), $this->invokePrivate($this->sharees, 'offset')) + ->willReturn($userResponse); + } else { + $this->groupManager->expects($this->once()) + ->method('getUserGroupIds') + ->with($user) + ->willReturn($groupResponse); + + $this->groupManager->expects($this->exactly(sizeof($groupResponse))) + ->method('displayNamesInGroup') + ->with($this->anything(), $searchTerm, $this->invokePrivate($this->sharees, 'limit'), $this->invokePrivate($this->sharees, 'offset')) + ->willReturnMap($userResponse); + } + + if ($singleUser !== false) { + $this->userManager->expects($this->once()) + ->method('get') + ->with($searchTerm) + ->willReturn($singleUser); + } + + $this->invokePrivate($this->sharees, 'getUsers', [$searchTerm]); + $result = $this->invokePrivate($this->sharees, 'result'); + + $this->assertEquals($exactExpected, $result['exact']['users']); + $this->assertEquals($expected, $result['users']); + $this->assertCount((int) $reachedEnd, $this->invokePrivate($this->sharees, 'reachedEndFor')); + } + + public function dataGetGroups() { + return [ + ['test', false, [], [], [], [], true, false], + [ + 'test', false, + [$this->getGroupMock('test1')], + [], + [], + [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], + true, + false, + ], + [ + 'test', false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [], + [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], + [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [], + [ + ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + null, + ], + [ + 'test', false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [ + ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], + ], + [ + ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + $this->getGroupMock('test'), + ], + ['test', true, [], [], [], [], true, false], + [ + 'test', true, + [ + $this->getGroupMock('test1'), + $this->getGroupMock('test2'), + ], + [$this->getGroupMock('test1')], + [], + [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', true, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test')], + [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], + [], + false, + false, + ], + [ + 'test', true, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test1')], + [], + [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', true, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']]], + [['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', true, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [], + [ + ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + null, + ], + [ + 'test', true, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [ + ['label' => 'test', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test']], + ], + [ + ['label' => 'test0', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + $this->getGroupMock('test'), + ], + ]; + } + + /** + * @dataProvider dataGetGroups + * + * @param string $searchTerm + * @param bool $shareWithGroupOnly + * @param array $groupResponse + * @param array $userGroupsResponse + * @param array $exactExpected + * @param array $expected + * @param bool $reachedEnd + * @param mixed $singleGroup + */ + public function testGetGroups($searchTerm, $shareWithGroupOnly, $groupResponse, $userGroupsResponse, $exactExpected, $expected, $reachedEnd, $singleGroup) { + $this->invokePrivate($this->sharees, 'limit', [2]); + $this->invokePrivate($this->sharees, 'offset', [0]); + $this->invokePrivate($this->sharees, 'shareWithGroupOnly', [$shareWithGroupOnly]); + + $this->groupManager->expects($this->once()) + ->method('search') + ->with($searchTerm, $this->invokePrivate($this->sharees, 'limit'), $this->invokePrivate($this->sharees, 'offset')) + ->willReturn($groupResponse); + + if ($singleGroup !== false) { + $this->groupManager->expects($this->once()) + ->method('get') + ->with($searchTerm) + ->willReturn($singleGroup); + } + + if ($shareWithGroupOnly) { + $user = $this->getUserMock('admin', 'Administrator'); + $this->session->expects($this->any()) + ->method('getUser') + ->willReturn($user); + + $numGetUserGroupsCalls = empty($groupResponse) ? 0 : 1; + $this->groupManager->expects($this->exactly($numGetUserGroupsCalls)) + ->method('getUserGroups') + ->with($user) + ->willReturn($userGroupsResponse); + } + + $this->invokePrivate($this->sharees, 'getGroups', [$searchTerm]); + $result = $this->invokePrivate($this->sharees, 'result'); + + $this->assertEquals($exactExpected, $result['exact']['groups']); + $this->assertEquals($expected, $result['groups']); + $this->assertCount((int) $reachedEnd, $this->invokePrivate($this->sharees, 'reachedEndFor')); + } + + public function dataGetRemote() { + return [ + ['test', [], [], [], true], + [ + 'test@remote', + [], + [ + ['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']], + ], + [], + true, + ], + [ + 'test', + [ + [ + 'FN' => 'User3 @ Localhost', + ], + [ + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + [], + [ + ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost']], + ], + true, + ], + [ + 'test@remote', + [ + [ + 'FN' => 'User3 @ Localhost', + ], + [ + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + [ + ['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']], + ], + [ + ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost']], + ], + true, + ], + [ + 'username@localhost', + [ + [ + 'FN' => 'User3 @ Localhost', + ], + [ + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + [ + ['label' => 'User @ Localhost', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost']], + ], + [ + ], + true, + ], + ]; + } + + /** + * @dataProvider dataGetRemote + * + * @param string $searchTerm + * @param array $contacts + * @param array $exactExpected + * @param array $expected + * @param bool $reachedEnd + */ + public function testGetRemote($searchTerm, $contacts, $exactExpected, $expected, $reachedEnd) { + $this->contactsManager->expects($this->any()) + ->method('search') + ->with($searchTerm, ['CLOUD', 'FN']) + ->willReturn($contacts); + + $this->invokePrivate($this->sharees, 'getRemote', [$searchTerm]); + $result = $this->invokePrivate($this->sharees, 'result'); + + $this->assertEquals($exactExpected, $result['exact']['remotes']); + $this->assertEquals($expected, $result['remotes']); + $this->assertCount((int) $reachedEnd, $this->invokePrivate($this->sharees, 'reachedEndFor')); + } + + public function dataSearch() { + $allTypes = [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE]; + + return [ + [[], '', true, '', null, $allTypes, 1, 200, false], + + // Test itemType + [[ + 'search' => '', + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'search' => 'foobar', + ], '', true, 'foobar', null, $allTypes, 1, 200, false], + [[ + 'search' => 0, + ], '', true, '0', null, $allTypes, 1, 200, false], + + // Test itemType + [[ + 'itemType' => '', + ], '', true, '', '', $allTypes, 1, 200, false], + [[ + 'itemType' => 'folder', + ], '', true, '', 'folder', $allTypes, 1, 200, false], + [[ + 'itemType' => 0, + ], '', true, '', '0', $allTypes, 1, 200, false], + + // Test shareType + [[ + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'shareType' => 0, + ], '', true, '', null, [0], 1, 200, false], + [[ + 'shareType' => '0', + ], '', true, '', null, [0], 1, 200, false], + [[ + 'shareType' => 1, + ], '', true, '', null, [1], 1, 200, false], + [[ + 'shareType' => 12, + ], '', true, '', null, [], 1, 200, false], + [[ + 'shareType' => 'foobar', + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'shareType' => [0, 1, 2], + ], '', true, '', null, [0, 1], 1, 200, false], + [[ + 'shareType' => [0, 1], + ], '', true, '', null, [0, 1], 1, 200, false], + [[ + 'shareType' => $allTypes, + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'shareType' => $allTypes, + ], '', false, '', null, [0, 1], 1, 200, false], + + // Test pagination + [[ + 'page' => 0, + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'page' => '0', + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'page' => -1, + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'page' => 1, + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'page' => 10, + ], '', true, '', null, $allTypes, 10, 200, false], + + // Test limit + [[ + 'limit' => 0, + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'limit' => '0', + ], '', true, '', null, $allTypes, 1, 200, false], + [[ + 'limit' => -1, + ], '', true, '', null, $allTypes, 1, 1, false], + [[ + 'limit' => 1, + ], '', true, '', null, $allTypes, 1, 1, false], + [[ + 'limit' => 10, + ], '', true, '', null, $allTypes, 1, 10, false], + + // Test $shareWithGroupOnly setting + [[], 'no', true, '', null, $allTypes, 1, 200, false], + [[], 'yes', true, '', null, $allTypes, 1, 200, true], + + ]; + } + + /** + * @dataProvider dataSearch + * + * @param array $getData + * @param string $apiSetting + * @param bool $remoteSharingEnabled + * @param string $search + * @param string $itemType + * @param array $shareTypes + * @param int $page + * @param int $perPage + * @param bool $shareWithGroupOnly + */ + public function testSearch($getData, $apiSetting, $remoteSharingEnabled, $search, $itemType, $shareTypes, $page, $perPage, $shareWithGroupOnly) { + $oldGet = $_GET; + $_GET = $getData; + + $config = $this->getMockBuilder('OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $config->expects($this->once()) + ->method('getAppValue') + ->with('core', 'shareapi_only_share_with_group_members', 'no') + ->willReturn($apiSetting); + + $sharees = $this->getMockBuilder('\OCA\Files_Sharing\API\Sharees') + ->setConstructorArgs([ + $this->groupManager, + $this->userManager, + $this->contactsManager, + $config, + $this->session, + $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('OCP\IRequest')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock() + ]) + ->setMethods(array('searchSharees', 'isRemoteSharingAllowed')) + ->getMock(); + $sharees->expects($this->once()) + ->method('searchSharees') + ->with($search, $itemType, $shareTypes, $page, $perPage) + ->willReturnCallback(function + ($isearch, $iitemType, $ishareTypes, $ipage, $iperPage) + use ($search, $itemType, $shareTypes, $page, $perPage) { + + // We are doing strict comparisons here, so we can differ 0/'' and null on shareType/itemType + $this->assertSame($search, $isearch); + $this->assertSame($itemType, $iitemType); + $this->assertSame($shareTypes, $ishareTypes); + $this->assertSame($page, $ipage); + $this->assertSame($perPage, $iperPage); + return new \OC_OCS_Result([]); + }); + $sharees->expects($this->any()) + ->method('isRemoteSharingAllowed') + ->with($itemType) + ->willReturn($remoteSharingEnabled); + + /** @var \PHPUnit_Framework_MockObject_MockObject|\OCA\Files_Sharing\API\Sharees $sharees */ + $this->assertInstanceOf('\OC_OCS_Result', $sharees->search()); + + $this->assertSame($shareWithGroupOnly, $this->invokePrivate($sharees, 'shareWithGroupOnly')); + + $_GET = $oldGet; + } + + public function dataIsRemoteSharingAllowed() { + return [ + ['file', true], + ['folder', true], + ['', false], + ['contacts', false], + ]; + } + + /** + * @dataProvider dataIsRemoteSharingAllowed + * + * @param string $itemType + * @param bool $expected + */ + public function testIsRemoteSharingAllowed($itemType, $expected) { + $this->assertSame($expected, $this->invokePrivate($this->sharees, 'isRemoteSharingAllowed', [$itemType])); + } + + public function dataSearchSharees() { + return [ + ['test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [], [], [], + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [], + 'groups' => [], + 'remotes' => [], + ], false], + ['test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [], [], [], + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [], + 'groups' => [], + 'remotes' => [], + ], false], + [ + 'test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [ + ['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], [ + ['label' => 'testgroup1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'testgroup1']], + ], [ + ['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']], + ], + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [ + ['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], + 'groups' => [ + ['label' => 'testgroup1', 'value' => ['shareType' => Share::SHARE_TYPE_GROUP, 'shareWith' => 'testgroup1']], + ], + 'remotes' => [ + ['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']], + ], + ], true, + ], + // No groups requested + [ + 'test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_REMOTE], 1, 2, false, [ + ['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], null, [ + ['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']], + ], + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [ + ['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], + 'groups' => [], + 'remotes' => [ + ['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']], + ], + ], false, + ], + // Share type restricted to user - Only one user + [ + 'test', 'folder', [Share::SHARE_TYPE_USER], 1, 2, false, [ + ['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], null, null, + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [ + ['label' => 'test One', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ], + 'groups' => [], + 'remotes' => [], + ], false, + ], + // Share type restricted to user - Multipage result + [ + 'test', 'folder', [Share::SHARE_TYPE_USER], 1, 2, false, [ + ['label' => 'test 1', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ['label' => 'test 2', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], + ], null, null, + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [ + ['label' => 'test 1', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ['label' => 'test 2', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], + ], + 'groups' => [], + 'remotes' => [], + ], true, + ], + ]; + } + + /** + * @dataProvider dataSearchSharees + * + * @param string $searchTerm + * @param string $itemType + * @param array $shareTypes + * @param int $page + * @param int $perPage + * @param bool $shareWithGroupOnly + * @param array $mockedUserResult + * @param array $mockedGroupsResult + * @param array $mockedRemotesResult + * @param array $expected + * @param bool $nextLink + */ + public function testSearchSharees($searchTerm, $itemType, array $shareTypes, $page, $perPage, $shareWithGroupOnly, + $mockedUserResult, $mockedGroupsResult, $mockedRemotesResult, $expected, $nextLink) { + /** @var \PHPUnit_Framework_MockObject_MockObject|\OCA\Files_Sharing\API\Sharees $sharees */ + $sharees = $this->getMockBuilder('\OCA\Files_Sharing\API\Sharees') + ->setConstructorArgs([ + $this->groupManager, + $this->userManager, + $this->contactsManager, + $this->getMockBuilder('OCP\IConfig')->disableOriginalConstructor()->getMock(), + $this->session, + $this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('OCP\IRequest')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock() + ]) + ->setMethods(array('getShareesForShareIds', 'getUsers', 'getGroups', 'getRemote')) + ->getMock(); + $sharees->expects(($mockedUserResult === null) ? $this->never() : $this->once()) + ->method('getUsers') + ->with($searchTerm) + ->willReturnCallback(function() use ($sharees, $mockedUserResult) { + $result = $this->invokePrivate($sharees, 'result'); + $result['users'] = $mockedUserResult; + $this->invokePrivate($sharees, 'result', [$result]); + }); + $sharees->expects(($mockedGroupsResult === null) ? $this->never() : $this->once()) + ->method('getGroups') + ->with($searchTerm) + ->willReturnCallback(function() use ($sharees, $mockedGroupsResult) { + $result = $this->invokePrivate($sharees, 'result'); + $result['groups'] = $mockedGroupsResult; + $this->invokePrivate($sharees, 'result', [$result]); + }); + $sharees->expects(($mockedRemotesResult === null) ? $this->never() : $this->once()) + ->method('getRemote') + ->with($searchTerm) + ->willReturnCallback(function() use ($sharees, $mockedRemotesResult) { + $result = $this->invokePrivate($sharees, 'result'); + $result['remotes'] = $mockedRemotesResult; + $this->invokePrivate($sharees, 'result', [$result]); + }); + + /** @var \OC_OCS_Result $ocs */ + $ocs = $this->invokePrivate($sharees, 'searchSharees', [$searchTerm, $itemType, $shareTypes, $page, $perPage, $shareWithGroupOnly]); + $this->assertInstanceOf('\OC_OCS_Result', $ocs); + $this->assertEquals($expected, $ocs->getData()); + + // Check if next link is set + if ($nextLink) { + $headers = $ocs->getHeaders(); + $this->assertArrayHasKey('Link', $headers); + $this->assertStringStartsWith('<', $headers['Link']); + $this->assertStringEndsWith('>; rel="next"', $headers['Link']); + } + } + + public function testSearchShareesNoItemType() { + /** @var \OC_OCS_Result $ocs */ + $ocs = $this->invokePrivate($this->sharees, 'searchSharees', ['', null, [], [], 0, 0, false]); + $this->assertInstanceOf('\OC_OCS_Result', $ocs); + + $this->assertSame(400, $ocs->getStatusCode(), 'Expected status code 400'); + $this->assertSame([], $ocs->getData(), 'Expected that no data is send'); + + $meta = $ocs->getMeta(); + $this->assertNotEmpty($meta); + $this->assertArrayHasKey('message', $meta); + $this->assertSame('missing itemType', $meta['message']); + } + + public function dataGetPaginationLink() { + return [ + [1, '/ocs/v1.php', ['limit' => 2], '; rel="next"'], + [10, '/ocs/v2.php', ['limit' => 2], '; rel="next"'], + ]; + } + + /** + * @dataProvider dataGetPaginationLink + * + * @param int $page + * @param string $scriptName + * @param array $params + * @param array $expected + */ + public function testGetPaginationLink($page, $scriptName, $params, $expected) { + $this->request->expects($this->once()) + ->method('getScriptName') + ->willReturn($scriptName); + + $this->assertEquals($expected, $this->invokePrivate($this->sharees, 'getPaginationLink', [$page, $params])); + } + + public function dataIsV2() { + return [ + ['/ocs/v1.php', false], + ['/ocs/v2.php', true], + ]; + } + + /** + * @dataProvider dataIsV2 + * + * @param string $scriptName + * @param bool $expected + */ + public function testIsV2($scriptName, $expected) { + $this->request->expects($this->once()) + ->method('getScriptName') + ->willReturn($scriptName); + + $this->assertEquals($expected, $this->invokePrivate($this->sharees, 'isV2')); + } +}