diff options
author | blizzz <blizzz@arthur-schiwon.de> | 2017-10-04 15:43:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-04 15:43:44 +0200 |
commit | 2d62f97f1bf1e7848e23ff0417915c3ab9e03e9a (patch) | |
tree | f1a67d6f764999fa802aa7034711321af684ddb9 /lib/private | |
parent | 5412c239f2ca128760c8d12de3bcd01384ddfd3f (diff) | |
parent | a579f8aa375bf02f9763892ce725713f08526a1a (diff) | |
download | nextcloud-server-2d62f97f1bf1e7848e23ff0417915c3ab9e03e9a.tar.gz nextcloud-server-2d62f97f1bf1e7848e23ff0417915c3ab9e03e9a.zip |
Merge pull request #6328 from nextcloud/split-sharees-api-logic
Splits off the logic from sharees endpoint thus making it available from within Nc/via PHP.
Diffstat (limited to 'lib/private')
-rw-r--r-- | lib/private/App/InfoParser.php | 6 | ||||
-rw-r--r-- | lib/private/Collaboration/Collaborators/GroupPlugin.php | 124 | ||||
-rw-r--r-- | lib/private/Collaboration/Collaborators/LookupPlugin.php | 85 | ||||
-rw-r--r-- | lib/private/Collaboration/Collaborators/MailPlugin.php | 164 | ||||
-rw-r--r-- | lib/private/Collaboration/Collaborators/RemotePlugin.php | 137 | ||||
-rw-r--r-- | lib/private/Collaboration/Collaborators/Search.php | 89 | ||||
-rw-r--r-- | lib/private/Collaboration/Collaborators/SearchResult.php | 86 | ||||
-rw-r--r-- | lib/private/Collaboration/Collaborators/UserPlugin.php | 149 | ||||
-rw-r--r-- | lib/private/Server.php | 25 | ||||
-rw-r--r-- | lib/private/legacy/app.php | 14 |
10 files changed, 879 insertions, 0 deletions
diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php index fef8ab7a1e8..0531682d67a 100644 --- a/lib/private/App/InfoParser.php +++ b/lib/private/App/InfoParser.php @@ -165,6 +165,12 @@ class InfoParser { if (isset($array['activity']['providers']['provider']) && is_array($array['activity']['providers']['provider'])) { $array['activity']['providers'] = $array['activity']['providers']['provider']; } + if (isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']) + && is_array($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']) + && !isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']['class']) + ) { + $array['collaboration']['collaborators']['searchPlugins'] = $array['collaboration']['collaborators']['searchPlugins']['searchPlugin']; + } if(!is_null($this->cache)) { $this->cache->set($fileCacheKey, json_encode($array)); diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php new file mode 100644 index 00000000000..0b2b3d71028 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php @@ -0,0 +1,124 @@ +<?php +/** + * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUserSession; +use OCP\Share; + +class GroupPlugin implements ISearchPlugin { + protected $shareeEnumeration; + protected $shareWithGroupOnly; + + /** @var IGroupManager */ + private $groupManager; + /** @var IConfig */ + private $config; + /** @var IUserSession */ + private $userSession; + + public function __construct(IConfig $config, IGroupManager $groupManager, IUserSession $userSession) { + $this->groupManager = $groupManager; + $this->config = $config; + $this->userSession = $userSession; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $hasMoreResults = false; + $result = ['wide' => [], 'exact' => []]; + + $groups = $this->groupManager->search($search, $limit, $offset); + $groupIds = array_map(function (IGroup $group) { return $group->getGID(); }, $groups); + + if (!$this->shareeEnumeration || sizeof($groups) < $limit) { + $hasMoreResults = true; + } + + $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); + $groupIds = array_intersect($groupIds, $userGroups); + } + + $lowerSearch = strtolower($search); + foreach ($groups as $group) { + // FIXME: use a more efficient approach + $gid = $group->getGID(); + if (!in_array($gid, $groupIds)) { + continue; + } + if (strtolower($gid) === $lowerSearch || strtolower($group->getDisplayName()) === $lowerSearch) { + $result['exact'][] = [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $gid, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $gid, + ], + ]; + } + } + + if ($offset === 0 && empty($result['exact'])) { + // 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($result['exact'], [ + 'label' => $group->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_GROUP, + 'shareWith' => $group->getGID(), + ], + ]); + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + $type = new SearchResultType('groups'); + $searchResult->addResultSet($type, $result['wide'], $result['exact']); + + return $hasMoreResults; + } +} diff --git a/lib/private/Collaboration/Collaborators/LookupPlugin.php b/lib/private/Collaboration/Collaborators/LookupPlugin.php new file mode 100644 index 00000000000..3a6a0943772 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/LookupPlugin.php @@ -0,0 +1,85 @@ +<?php +/** + * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\Share; + +class LookupPlugin implements ISearchPlugin { + + /** @var IConfig */ + private $config; + /** @var IClientService */ + private $clientService; + + public function __construct(IConfig $config, IClientService $clientService) { + $this->config = $config; + $this->clientService = $clientService; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + if ($this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no') !== 'yes') { + return false; + } + + $lookupServerUrl = $this->config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com'); + $lookupServerUrl = rtrim($lookupServerUrl, '/'); + $result = []; + + try { + $client = $this->clientService->newClient(); + $response = $client->get( + $lookupServerUrl . '/users?search=' . urlencode($search), + [ + 'timeout' => 10, + 'connect_timeout' => 3, + ] + ); + + $body = json_decode($response->getBody(), true); + + foreach ($body as $lookup) { + $result[] = [ + 'label' => $lookup['federationId'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $lookup['federationId'], + ], + 'extra' => $lookup, + ]; + } + } catch (\Exception $e) { + } + + $type = new SearchResultType('lookup'); + $searchResult->addResultSet($type, $result, []); + + return false; + } +} diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php new file mode 100644 index 00000000000..d28bd3692a4 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -0,0 +1,164 @@ +<?php +/** + * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Contacts\IManager; +use OCP\Federation\ICloudIdManager; +use OCP\IConfig; +use OCP\Share; + +class MailPlugin implements ISearchPlugin { + protected $shareeEnumeration; + + /** @var IManager */ + private $contactsManager; + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var IConfig */ + private $config; + + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) { + $this->contactsManager = $contactsManager; + $this->cloudIdManager = $cloudIdManager; + $this->config = $config; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + /** + * @param $search + * @param $limit + * @param $offset + * @param ISearchResult $searchResult + * @return bool + * @since 13.0.0 + */ + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + $userType = new SearchResultType('users'); + $emailType = new SearchResultType('emails'); + + // Search in contacts + //@todo Pagination missing + $addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN']); + $lowerSearch = strtolower($search); + foreach ($addressBookContacts as $contact) { + if (isset($contact['EMAIL'])) { + $emailAddresses = $contact['EMAIL']; + if (!is_array($emailAddresses)) { + $emailAddresses = [$emailAddresses]; + } + foreach ($emailAddresses as $emailAddress) { + $exactEmailMatch = strtolower($emailAddress) === $lowerSearch; + + if (isset($contact['isLocalSystemBook'])) { + if ($exactEmailMatch) { + try { + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$searchResult->hasResult($userType, $cloud->getUser())) { + $singleResult = [[ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $cloud->getUser(), + ], + ]]; + $searchResult->addResultSet($userType, [], $singleResult); + $searchResult->markExactIdMatch($emailType); + } + return false; + } + + if ($this->shareeEnumeration) { + try { + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (!$searchResult->hasResult($userType, $cloud->getUser())) { + $singleResult = [[ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $cloud->getUser(), + ]], + ]; + $searchResult->addResultSet($userType, $singleResult, []); + } + } + continue; + } + + if ($exactEmailMatch || strtolower($contact['FN']) === $lowerSearch) { + if ($exactEmailMatch) { + $searchResult->markExactIdMatch($emailType); + } + $result['exact'][] = [ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_EMAIL, + 'shareWith' => $emailAddress, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $contact['FN'] . " ($emailAddress)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_EMAIL, + 'shareWith' => $emailAddress, + ], + ]; + } + } + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) { + $result['exact'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_EMAIL, + 'shareWith' => $search, + ], + ]; + } + + $searchResult->addResultSet($emailType, $result['wide'], $result['exact']); + + return true; + } +} diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php new file mode 100644 index 00000000000..b17a64e4ff1 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -0,0 +1,137 @@ +<?php +/** + * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Contacts\IManager; +use OCP\Federation\ICloudIdManager; +use OCP\IConfig; +use OCP\Share; + +class RemotePlugin implements ISearchPlugin { + protected $shareeEnumeration; + + /** @var IManager */ + private $contactsManager; + /** @var ICloudIdManager */ + private $cloudIdManager; + /** @var IConfig */ + private $config; + + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) { + $this->contactsManager = $contactsManager; + $this->cloudIdManager = $cloudIdManager; + $this->config = $config; + + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + $resultType = new SearchResultType('remotes'); + + // Search in contacts + //@todo Pagination missing + $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN']); + foreach ($addressBookContacts as $contact) { + if (isset($contact['isLocalSystemBook'])) { + continue; + } + if (isset($contact['CLOUD'])) { + $cloudIds = $contact['CLOUD']; + if (!is_array($cloudIds)) { + $cloudIds = [$cloudIds]; + } + $lowerSearch = strtolower($search); + foreach ($cloudIds as $cloudId) { + try { + list(, $serverUrl) = $this->splitUserRemote($cloudId); + } catch (\InvalidArgumentException $e) { + continue; + } + + if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) { + if (strtolower($cloudId) === $lowerSearch) { + $searchResult->markExactIdMatch($resultType); + } + $result['exact'][] = [ + 'label' => $contact['FN'] . " ($cloudId)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $contact['FN'] . " ($cloudId)", + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $cloudId, + 'server' => $serverUrl, + ], + ]; + } + } + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) { + $result['exact'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $search, + ], + ]; + } + + $searchResult->addResultSet($resultType, $result['wide'], $result['exact']); + + return true; + } + + /** + * split user and remote from federated cloud id + * + * @param string $address federated share address + * @return array [user, remoteURL] + * @throws \InvalidArgumentException + */ + public function splitUserRemote($address) { + try { + $cloudId = $this->cloudIdManager->resolveCloudId($address); + return [$cloudId->getUser(), $cloudId->getRemote()]; + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e); + } + } +} diff --git a/lib/private/Collaboration/Collaborators/Search.php b/lib/private/Collaboration/Collaborators/Search.php new file mode 100644 index 00000000000..e9b15dd1201 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/Search.php @@ -0,0 +1,89 @@ +<?php +/** + * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Collaboration\Collaborators; + +use OCP\Collaboration\Collaborators\ISearch; +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IContainer; +use OCP\Share; + +class Search implements ISearch { + /** @var IContainer */ + private $c; + + protected $pluginList = []; + + public function __construct(IContainer $c) { + $this->c = $c; + } + + public function search($search, array $shareTypes, $lookup, $limit, $offset) { + $hasMoreResults = false; + + /** @var ISearchResult $searchResult */ + $searchResult = $this->c->resolve(SearchResult::class); + + foreach ($shareTypes as $type) { + if(!isset($this->pluginList[$type])) { + continue; + } + foreach ($this->pluginList[$type] as $plugin) { + /** @var ISearchPlugin $searchPlugin */ + $searchPlugin = $this->c->resolve($plugin); + $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + } + } + + // Get from lookup server, not a separate share type + if ($lookup) { + $searchPlugin = $this->c->resolve(LookupPlugin::class); + $hasMoreResults |= $searchPlugin->search($search, $limit, $offset, $searchResult); + } + + // sanitizing, could go into the plugins as well + + // if we have a exact match, either for the federated cloud id or for the + // email address we only return the exact match. It is highly unlikely + // that the exact same email address and federated cloud id exists + $emailType = new SearchResultType('emails'); + $remoteType = new SearchResultType('remotes'); + if($searchResult->hasExactIdMatch($emailType) && !$searchResult->hasExactIdMatch($remoteType)) { + $searchResult->unsetResult($remoteType); + } elseif (!$searchResult->hasExactIdMatch($emailType) && $searchResult->hasExactIdMatch($remoteType)) { + $searchResult->unsetResult($emailType); + } + + return [$searchResult->asArray(), (bool)$hasMoreResults]; + } + + public function registerPlugin(array $pluginInfo) { + $shareType = constant(Share::class . '::' . $pluginInfo['shareType']); + if($shareType === null) { + throw new \InvalidArgumentException('Provided ShareType is invalid'); + } + $this->pluginList[$shareType][] = $pluginInfo['class']; + } +} diff --git a/lib/private/Collaboration/Collaborators/SearchResult.php b/lib/private/Collaboration/Collaborators/SearchResult.php new file mode 100644 index 00000000000..7b32b388203 --- /dev/null +++ b/lib/private/Collaboration/Collaborators/SearchResult.php @@ -0,0 +1,86 @@ +<?php +/** + * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; + +class SearchResult implements ISearchResult { + + protected $result = [ + 'exact' => [], + ]; + + protected $exactIdMatches = []; + + public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null) { + $type = $type->getLabel(); + if(!isset($this->result[$type])) { + $this->result[$type] = []; + $this->result['exact'][$type] = []; + } + + $this->result[$type] = array_merge($this->result[$type], $matches); + if(is_array($exactMatches)) { + $this->result['exact'][$type] = array_merge($this->result['exact'][$type], $exactMatches); + } + } + + public function markExactIdMatch(SearchResultType $type) { + $this->exactIdMatches[$type->getLabel()] = 1; + } + + public function hasExactIdMatch(SearchResultType$type) { + return isset($this->exactIdMatches[$type->getLabel()]); + } + + public function hasResult(SearchResultType $type, $collaboratorId) { + $type = $type->getLabel(); + if(!isset($this->result[$type])) { + return false; + } + + $resultArrays = [$this->result['exact'][$type], $this->result[$type]]; + foreach($resultArrays as $resultArray) { + if ($resultArray['value']['shareWith'] === $collaboratorId) { + return true; + } + } + + return false; + } + + public function asArray() { + return $this->result; + } + + public function unsetResult(SearchResultType $type) { + $type = $type->getLabel(); + $this->result[$type] = []; + if(isset($this->result['exact'][$type])) { + $this->result['exact'][$type] = []; + } + } +} diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php new file mode 100644 index 00000000000..86a55aa428f --- /dev/null +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -0,0 +1,149 @@ +<?php +/** + * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Collaboration\Collaborators; + + +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share; + +class UserPlugin implements ISearchPlugin { + /* @var bool */ + protected $shareWithGroupOnly; + protected $shareeEnumeration; + + /** @var IConfig */ + private $config; + /** @var IGroupManager */ + private $groupManager; + /** @var IUserSession */ + private $userSession; + /** @var IUserManager */ + private $userManager; + + public function __construct(IConfig $config, IUserManager $userManager, IGroupManager $groupManager, IUserSession $userSession) { + $this->config = $config; + + $this->groupManager = $groupManager; + $this->userSession = $userSession; + $this->userManager = $userManager; + + $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; + } + + public function search($search, $limit, $offset, ISearchResult $searchResult) { + $result = ['wide' => [], 'exact' => []]; + $users = []; + $hasMoreResults = false; + + $userGroups = []; + 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, $limit, $offset); + foreach ($usersTmp as $uid => $userDisplayName) { + $users[$uid] = $userDisplayName; + } + } + } else { + // Search in all users + $usersTmp = $this->userManager->searchDisplayName($search, $limit, $offset); + + foreach ($usersTmp as $user) { + $users[$user->getUID()] = $user->getDisplayName(); + } + } + + if (!$this->shareeEnumeration || sizeof($users) < $limit) { + $hasMoreResults = true; + } + + $foundUserById = false; + $lowerSearch = strtolower($search); + foreach ($users as $uid => $userDisplayName) { + if (strtolower($uid) === $lowerSearch || strtolower($userDisplayName) === $lowerSearch) { + if (strtolower($uid) === $lowerSearch) { + $foundUserById = true; + } + $result['exact'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $uid, + ], + ]; + } else { + $result['wide'][] = [ + 'label' => $userDisplayName, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $uid, + ], + ]; + } + } + + if ($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) { + $addUser = true; + + if ($this->shareWithGroupOnly) { + // Only add, if we have a common group + $commonGroups = array_intersect($userGroups, $this->groupManager->getUserGroupIds($user)); + $addUser = !empty($commonGroups); + } + + if ($addUser) { + array_push($result['exact'], [ + 'label' => $user->getDisplayName(), + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $user->getUID(), + ], + ]); + } + } + } + + if (!$this->shareeEnumeration) { + $result['wide'] = []; + } + + $type = new SearchResultType('users'); + $searchResult->addResultSet($type, $result['wide'], $result['exact']); + + return $hasMoreResults; + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index a20d9ccfc01..29aee06d896 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -52,6 +52,10 @@ use OC\AppFramework\Http\Request; use OC\AppFramework\Utility\SimpleContainer; use OC\AppFramework\Utility\TimeFactory; use OC\Authentication\LoginCredentials\Store; +use OC\Collaboration\Collaborators\GroupPlugin; +use OC\Collaboration\Collaborators\MailPlugin; +use OC\Collaboration\Collaborators\RemotePlugin; +use OC\Collaboration\Collaborators\UserPlugin; use OC\Command\CronBus; use OC\Contacts\ContactsMenu\ActionFactory; use OC\Diagnostics\EventLogger; @@ -115,6 +119,7 @@ use OCP\Contacts\ContactsMenu\IActionFactory; use OCP\Lock\ILockingProvider; use OCP\RichObjectStrings\IValidator; use OCP\Security\IContentSecurityPolicyManager; +use OCP\Share; use OCP\Share\IShareHelper; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -993,6 +998,19 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias('ShareManager', \OCP\Share\IManager::class); + $this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function(Server $c) { + $instance = new Collaboration\Collaborators\Search($c); + + // register default plugins + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailPlugin::class]); + $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]); + + return $instance; + }); + $this->registerAlias('CollaboratorSearch', \OCP\Collaboration\Collaborators\ISearch::class); + $this->registerService('SettingsManager', function (Server $c) { $manager = new \OC\Settings\Manager( $c->getLogger(), @@ -1777,6 +1795,13 @@ class Server extends ServerContainer implements IServerContainer { } /** + * @return \OCP\Collaboration\Collaborators\ISearch + */ + public function getCollaboratorSearch() { + return $this->query('CollaboratorSearch'); + } + + /** * Returns the LDAP Provider * * @return \OCP\LDAP\ILDAPProvider diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index 24c7a344e00..a33c9be20fc 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -174,6 +174,20 @@ class OC_App { \OC::$server->getActivityManager()->registerProvider($provider); } } + if (!empty($info['collaboration']['plugins'])) { + // deal with one or many plugin entries + $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ? + [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin']; + foreach ($plugins as $plugin) { + if($plugin['@attributes']['type'] === 'collaborator-search') { + $pluginInfo = [ + 'shareType' => $plugin['@attributes']['share-type'], + 'class' => $plugin['@value'], + ]; + \OC::$server->getCollaboratorSearch()->registerPlugin($pluginInfo); + } + } + } } /** |