diff options
Diffstat (limited to 'lib/private/Collaboration')
19 files changed, 857 insertions, 715 deletions
diff --git a/lib/private/Collaboration/AutoComplete/Manager.php b/lib/private/Collaboration/AutoComplete/Manager.php index cab15baf535..cc5df78beea 100644 --- a/lib/private/Collaboration/AutoComplete/Manager.php +++ b/lib/private/Collaboration/AutoComplete/Manager.php @@ -1,75 +1,67 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\AutoComplete; use OCP\Collaboration\AutoComplete\IManager; use OCP\Collaboration\AutoComplete\ISorter; -use OCP\IServerContainer; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; class Manager implements IManager { /** @var string[] */ - protected $sorters = []; + protected array $sorters = []; - /** @var ISorter[] */ - protected $sorterInstances = []; - /** @var IServerContainer */ - private $c; + /** @var ISorter[] */ + protected array $sorterInstances = []; - public function __construct(IServerContainer $container) { - $this->c = $container; + public function __construct( + private ContainerInterface $container, + private LoggerInterface $logger, + ) { } - public function runSorters(array $sorters, array &$sortArray, array $context) { + public function runSorters(array $sorters, array &$sortArray, array $context): void { $sorterInstances = $this->getSorters(); while ($sorter = array_shift($sorters)) { if (isset($sorterInstances[$sorter])) { $sorterInstances[$sorter]->sort($sortArray, $context); } else { - $this->c->getLogger()->warning('No sorter for ID "{id}", skipping', [ + $this->logger->warning('No sorter for ID "{id}", skipping', [ 'app' => 'core', 'id' => $sorter ]); } } } - public function registerSorter($className) { + public function registerSorter($className): void { $this->sorters[] = $className; } - protected function getSorters() { + protected function getSorters(): array { if (count($this->sorterInstances) === 0) { foreach ($this->sorters as $sorter) { - /** @var ISorter $instance */ - $instance = $this->c->resolve($sorter); + try { + $instance = $this->container->get($sorter); + } catch (ContainerExceptionInterface) { + $this->logger->notice( + 'Skipping not registered sorter. Class name: {class}', + ['app' => 'core', 'class' => $sorter], + ); + continue; + } if (!$instance instanceof ISorter) { - $this->c->getLogger()->notice('Skipping sorter which is not an instance of ISorter. Class name: {class}', + $this->logger->notice('Skipping sorter which is not an instance of ISorter. Class name: {class}', ['app' => 'core', 'class' => $sorter]); continue; } $sorterId = trim($instance->getId()); if (trim($sorterId) === '') { - $this->c->getLogger()->notice('Skipping sorter with empty ID. Class name: {class}', + $this->logger->notice('Skipping sorter with empty ID. Class name: {class}', ['app' => 'core', 'class' => $sorter]); continue; } diff --git a/lib/private/Collaboration/Collaborators/GroupPlugin.php b/lib/private/Collaboration/Collaborators/GroupPlugin.php index 75e52c19e0b..a59d5981825 100644 --- a/lib/private/Collaboration/Collaborators/GroupPlugin.php +++ b/lib/private/Collaboration/Collaborators/GroupPlugin.php @@ -1,29 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -37,34 +16,31 @@ use OCP\IUserSession; use OCP\Share\IShare; class GroupPlugin implements ISearchPlugin { - /** @var bool */ - protected $shareeEnumeration; - /** @var bool */ - protected $shareWithGroupOnly; - /** @var bool */ - protected $shareeEnumerationInGroupOnly; - /** @var bool */ - protected $groupSharingDisabled; - - /** @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; + protected bool $shareeEnumeration; + + protected bool $shareWithGroupOnly; + + protected bool $shareeEnumerationInGroupOnly; + + protected bool $groupSharingDisabled; + public function __construct( + private IConfig $config, + private IGroupManager $groupManager, + private IUserSession $userSession, + private mixed $shareWithGroupOnlyExcludeGroupsList = [], + ) { $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'; $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; $this->groupSharingDisabled = $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'no'; + + if ($this->shareWithGroupOnly) { + $this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? []; + } } - public function search($search, $limit, $offset, ISearchResult $searchResult) { + public function search($search, $limit, $offset, ISearchResult $searchResult): bool { if ($this->groupSharingDisabled) { return false; } @@ -89,6 +65,9 @@ class GroupPlugin implements ISearchPlugin { return $group->getGID(); }, $userGroups); $groupIds = array_intersect($groupIds, $userGroups); + + // ShareWithGroupOnly filtering + $groupIds = array_diff($groupIds, $this->shareWithGroupOnlyExcludeGroupsList); } $lowerSearch = strtolower($search); diff --git a/lib/private/Collaboration/Collaborators/LookupPlugin.php b/lib/private/Collaboration/Collaborators/LookupPlugin.php index 72cbfd4de4b..fb6b9f2e0e8 100644 --- a/lib/private/Collaboration/Collaborators/LookupPlugin.php +++ b/lib/private/Collaboration/Collaborators/LookupPlugin.php @@ -1,29 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author J0WI <J0WI@users.noreply.github.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -38,42 +17,33 @@ use OCP\Share\IShare; use Psr\Log\LoggerInterface; class LookupPlugin implements ISearchPlugin { - - /** @var IConfig */ - private $config; - /** @var IClientService */ - private $clientService; /** @var string remote part of the current user's cloud id */ - private $currentUserRemote; - /** @var ICloudIdManager */ - private $cloudIdManager; - /** @var LoggerInterface */ - private $logger; + private string $currentUserRemote; - public function __construct(IConfig $config, - IClientService $clientService, - IUserSession $userSession, - ICloudIdManager $cloudIdManager, - LoggerInterface $logger) { - $this->config = $config; - $this->clientService = $clientService; - $this->cloudIdManager = $cloudIdManager; + public function __construct( + private IConfig $config, + private IClientService $clientService, + IUserSession $userSession, + private ICloudIdManager $cloudIdManager, + private LoggerInterface $logger, + ) { $currentUserCloudId = $userSession->getUser()->getCloudId(); $this->currentUserRemote = $cloudIdManager->resolveCloudId($currentUserCloudId)->getRemote(); - $this->logger = $logger; } - public function search($search, $limit, $offset, ISearchResult $searchResult) { - $isGlobalScaleEnabled = $this->config->getSystemValue('gs.enabled', false); - $isLookupServerEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes') === 'yes'; + public function search($search, $limit, $offset, ISearchResult $searchResult): bool { + $isGlobalScaleEnabled = $this->config->getSystemValueBool('gs.enabled', false); + $isLookupServerEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no') === 'yes'; $hasInternetConnection = $this->config->getSystemValueBool('has_internet_connection', true); - // if case of Global Scale we always search the lookup server - if (!$isGlobalScaleEnabled && (!$isLookupServerEnabled || !$hasInternetConnection)) { + // If case of Global Scale we always search the lookup server + // TODO: Reconsider using the lookup server for non-global scale + // if (!$isGlobalScaleEnabled && (!$isLookupServerEnabled || !$hasInternetConnection || $disableLookupServer)) { + if (!$isGlobalScaleEnabled) { return false; } - $lookupServerUrl = $this->config->getSystemValue('lookup_server', 'https://lookup.nextcloud.com'); + $lookupServerUrl = $this->config->getSystemValueString('lookup_server', 'https://lookup.nextcloud.com'); if (empty($lookupServerUrl)) { return false; } @@ -96,7 +66,7 @@ class LookupPlugin implements ISearchPlugin { try { $remote = $this->cloudIdManager->resolveCloudId($lookup['federationId'])->getRemote(); } catch (\Exception $e) { - $this->logger->error('Can not parse federated cloud ID "' . $lookup['federationId'] . '"', [ + $this->logger->error('Can not parse federated cloud ID "' . $lookup['federationId'] . '"', [ 'exception' => $e, ]); continue; @@ -104,7 +74,7 @@ class LookupPlugin implements ISearchPlugin { if ($this->currentUserRemote === $remote) { continue; } - $name = isset($lookup['name']['value']) ? $lookup['name']['value'] : ''; + $name = $lookup['name']['value'] ?? ''; $label = empty($name) ? $lookup['federationId'] : $name . ' (' . $lookup['federationId'] . ')'; $result[] = [ 'label' => $label, diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index aae6f305981..55e3945ace2 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -1,28 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Tobia De Koninck <tobia@ledfan.be> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -37,63 +17,58 @@ use OCP\IConfig; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; -use OCP\Share\IShare; use OCP\Mail\IMailer; +use OCP\Share\IShare; class MailPlugin implements ISearchPlugin { - /* @var bool */ - protected $shareWithGroupOnly; - /* @var bool */ - protected $shareeEnumeration; - /* @var bool */ - protected $shareeEnumerationInGroupOnly; - /* @var bool */ - protected $shareeEnumerationPhone; - /* @var bool */ - protected $shareeEnumerationFullMatch; - - /** @var IManager */ - private $contactsManager; - /** @var ICloudIdManager */ - private $cloudIdManager; - /** @var IConfig */ - private $config; - - /** @var IGroupManager */ - private $groupManager; - /** @var KnownUserService */ - private $knownUserService; - /** @var IUserSession */ - private $userSession; - /** @var IMailer */ - private $mailer; - - public function __construct(IManager $contactsManager, - ICloudIdManager $cloudIdManager, - IConfig $config, - IGroupManager $groupManager, - KnownUserService $knownUserService, - IUserSession $userSession, - IMailer $mailer) { - $this->contactsManager = $contactsManager; - $this->cloudIdManager = $cloudIdManager; - $this->config = $config; - $this->groupManager = $groupManager; - $this->knownUserService = $knownUserService; - $this->userSession = $userSession; - $this->mailer = $mailer; + protected bool $shareWithGroupOnly; + + protected bool $shareeEnumeration; + + protected bool $shareeEnumerationInGroupOnly; + protected bool $shareeEnumerationPhone; + + protected bool $shareeEnumerationFullMatch; + + protected bool $shareeEnumerationFullMatchEmail; + + public function __construct( + private IManager $contactsManager, + private ICloudIdManager $cloudIdManager, + private IConfig $config, + private IGroupManager $groupManager, + private KnownUserService $knownUserService, + private IUserSession $userSession, + private IMailer $mailer, + private mixed $shareWithGroupOnlyExcludeGroupsList = [], + ) { $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'; $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; $this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes'; + $this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes'; + + if ($this->shareWithGroupOnly) { + $this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? []; + } } /** * {@inheritdoc} */ - public function search($search, $limit, $offset, ISearchResult $searchResult) { + public function search($search, $limit, $offset, ISearchResult $searchResult): bool { + if ($this->shareeEnumerationFullMatch && !$this->shareeEnumerationFullMatchEmail) { + return false; + } + + // Extract the email address from "Foo Bar <foo.bar@example.tld>" and then search with "foo.bar@example.tld" instead + $result = preg_match('/<([^@]+@.+)>$/', $search, $matches); + if ($result && filter_var($matches[1], FILTER_VALIDATE_EMAIL)) { + return $this->search($matches[1], $limit, $offset, $searchResult); + } + $currentUserId = $this->userSession->getUser()->getUID(); $result = $userResults = ['wide' => [], 'exact' => []]; @@ -107,8 +82,8 @@ class MailPlugin implements ISearchPlugin { [ 'limit' => $limit, 'offset' => $offset, - 'enumeration' => (bool) $this->shareeEnumeration, - 'fullmatch' => (bool) $this->shareeEnumerationFullMatch, + 'enumeration' => $this->shareeEnumeration, + 'fullmatch' => $this->shareeEnumerationFullMatch, ] ); $lowerSearch = strtolower($search); @@ -126,6 +101,11 @@ class MailPlugin implements ISearchPlugin { $emailAddress = $emailAddressData['value']; $emailAddressType = $emailAddressData['type']; } + + if (!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) { + continue; + } + if (isset($contact['FN'])) { $displayName = $contact['FN'] . ' (' . $emailAddress . ')'; } @@ -137,6 +117,10 @@ class MailPlugin implements ISearchPlugin { * Check if the user may share with the user associated with the e-mail of the just found contact */ $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); + + // ShareWithGroupOnly filtering + $userGroups = array_diff($userGroups, $this->shareWithGroupOnlyExcludeGroupsList); + $found = false; foreach ($userGroups as $userGroup) { if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) { @@ -150,7 +134,7 @@ class MailPlugin implements ISearchPlugin { } if ($exactEmailMatch && $this->shareeEnumerationFullMatch) { try { - $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? ''); } catch (\InvalidArgumentException $e) { continue; } @@ -158,8 +142,8 @@ class MailPlugin implements ISearchPlugin { if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { $singleResult = [[ 'label' => $displayName, - 'uuid' => $contact['UID'], - 'name' => $contact['FN'], + 'uuid' => $contact['UID'] ?? $emailAddress, + 'name' => $contact['FN'] ?? $displayName, 'value' => [ 'shareType' => IShare::TYPE_USER, 'shareWith' => $cloud->getUser(), @@ -175,7 +159,7 @@ class MailPlugin implements ISearchPlugin { if ($this->shareeEnumeration) { try { - $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]); + $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? ''); } catch (\InvalidArgumentException $e) { continue; } @@ -198,8 +182,8 @@ class MailPlugin implements ISearchPlugin { if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { $userResults['wide'][] = [ 'label' => $displayName, - 'uuid' => $contact['UID'], - 'name' => $contact['FN'], + 'uuid' => $contact['UID'] ?? $emailAddress, + 'name' => $contact['FN'] ?? $displayName, 'value' => [ 'shareType' => IShare::TYPE_USER, 'shareWith' => $cloud->getUser(), @@ -219,8 +203,8 @@ class MailPlugin implements ISearchPlugin { } $result['exact'][] = [ 'label' => $displayName, - 'uuid' => $contact['UID'], - 'name' => $contact['FN'], + 'uuid' => $contact['UID'] ?? $emailAddress, + 'name' => $contact['FN'] ?? $displayName, 'type' => $emailAddressType ?? '', 'value' => [ 'shareType' => IShare::TYPE_EMAIL, @@ -230,8 +214,8 @@ class MailPlugin implements ISearchPlugin { } else { $result['wide'][] = [ 'label' => $displayName, - 'uuid' => $contact['UID'], - 'name' => $contact['FN'], + 'uuid' => $contact['UID'] ?? $emailAddress, + 'name' => $contact['FN'] ?? $displayName, 'type' => $emailAddressType ?? '', 'value' => [ 'shareType' => IShare::TYPE_EMAIL, @@ -245,8 +229,8 @@ class MailPlugin implements ISearchPlugin { $reachedEnd = true; if ($this->shareeEnumeration) { - $reachedEnd = (count($result['wide']) < $offset + $limit) && - (count($userResults['wide']) < $offset + $limit); + $reachedEnd = (count($result['wide']) < $offset + $limit) + && (count($userResults['wide']) < $offset + $limit); $result['wide'] = array_slice($result['wide'], $offset, $limit); $userResults['wide'] = array_slice($userResults['wide'], $offset, $limit); @@ -273,6 +257,6 @@ class MailPlugin implements ISearchPlugin { public function isCurrentUser(ICloudId $cloud): bool { $currentUser = $this->userSession->getUser(); - return $currentUser instanceof IUser ? $currentUser->getUID() === $cloud->getUser() : false; + return $currentUser instanceof IUser && $currentUser->getUID() === $cloud->getUser(); } } diff --git a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php index 413799e52c6..f4c1793ea0a 100644 --- a/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php +++ b/lib/private/Collaboration/Collaborators/RemoteGroupPlugin.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -33,14 +15,12 @@ use OCP\Share; use OCP\Share\IShare; class RemoteGroupPlugin implements ISearchPlugin { - protected $shareeEnumeration; - - /** @var ICloudIdManager */ - private $cloudIdManager; - /** @var bool */ - private $enabled = false; + private bool $enabled = false; - public function __construct(ICloudFederationProviderManager $cloudFederationProviderManager, ICloudIdManager $cloudIdManager) { + public function __construct( + ICloudFederationProviderManager $cloudFederationProviderManager, + private ICloudIdManager $cloudIdManager, + ) { try { $fileSharingProvider = $cloudFederationProviderManager->getCloudFederationProvider('file'); $supportedShareTypes = $fileSharingProvider->getSupportedShareTypes(); @@ -50,10 +30,9 @@ class RemoteGroupPlugin implements ISearchPlugin { } catch (\Exception $e) { // do nothing, just don't enable federated group shares } - $this->cloudIdManager = $cloudIdManager; } - public function search($search, $limit, $offset, ISearchResult $searchResult) { + public function search($search, $limit, $offset, ISearchResult $searchResult): bool { $result = ['wide' => [], 'exact' => []]; $resultType = new SearchResultType('remote_groups'); @@ -83,7 +62,7 @@ class RemoteGroupPlugin implements ISearchPlugin { * @return array [user, remoteURL] * @throws \InvalidArgumentException */ - public function splitGroupRemote($address) { + public function splitGroupRemote($address): array { try { $cloudId = $this->cloudIdManager->resolveCloudId($address); return [$cloudId->getUser(), $cloudId->getRemote()]; diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php index 7d7a013a38c..037c6f6cbea 100644 --- a/lib/private/Collaboration/Collaborators/RemotePlugin.php +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -1,28 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Julius Härtl <jus@bitgrid.net> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -37,32 +17,22 @@ use OCP\IUserSession; use OCP\Share\IShare; class RemotePlugin implements ISearchPlugin { - protected $shareeEnumeration; + protected bool $shareeEnumeration; - /** @var IManager */ - private $contactsManager; - /** @var ICloudIdManager */ - private $cloudIdManager; - /** @var IConfig */ - private $config; - /** @var IUserManager */ - private $userManager; - /** @var string */ - private $userId = ''; + private string $userId; - public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) { - $this->contactsManager = $contactsManager; - $this->cloudIdManager = $cloudIdManager; - $this->config = $config; - $this->userManager = $userManager; - $user = $userSession->getUser(); - if ($user !== null) { - $this->userId = $user->getUID(); - } + public function __construct( + private IManager $contactsManager, + private ICloudIdManager $cloudIdManager, + private IConfig $config, + private IUserManager $userManager, + IUserSession $userSession, + ) { + $this->userId = $userSession->getUser()?->getUID() ?? ''; $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; } - public function search($search, $limit, $offset, ISearchResult $searchResult) { + public function search($search, $limit, $offset, ISearchResult $searchResult): bool { $result = ['wide' => [], 'exact' => []]; $resultType = new SearchResultType('remotes'); @@ -185,10 +155,10 @@ class RemotePlugin implements ISearchPlugin { * @return array [user, remoteURL] * @throws \InvalidArgumentException */ - public function splitUserRemote($address) { + public function splitUserRemote(string $address): array { try { $cloudId = $this->cloudIdManager->resolveCloudId($address); - return [$cloudId->getUser(), $cloudId->getRemote()]; + return [$cloudId->getUser(), $this->cloudIdManager->removeProtocolFromUrl($cloudId->getRemote(), true)]; } 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 index b0ee09356af..ea39f885fc6 100644 --- a/lib/private/Collaboration/Collaborators/Search.php +++ b/lib/private/Collaboration/Collaborators/Search.php @@ -1,29 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author onehappycat <one.happy.cat@gmx.com> - * @author Robin Appelman <robin@icewind.nl> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -32,35 +11,31 @@ use OCP\Collaboration\Collaborators\ISearchPlugin; use OCP\Collaboration\Collaborators\ISearchResult; use OCP\Collaboration\Collaborators\SearchResultType; use OCP\IContainer; -use OCP\Share; +use OCP\Share\IShare; class Search implements ISearch { - /** @var IContainer */ - private $c; - - protected $pluginList = []; + protected array $pluginList = []; - public function __construct(IContainer $c) { - $this->c = $c; + public function __construct( + private IContainer $container, + ) { } /** * @param string $search - * @param array $shareTypes * @param bool $lookup * @param int|null $limit * @param int|null $offset - * @return array * @throws \OCP\AppFramework\QueryException */ - public function search($search, array $shareTypes, $lookup, $limit, $offset) { + public function search($search, array $shareTypes, $lookup, $limit, $offset): array { $hasMoreResults = false; // Trim leading and trailing whitespace characters, e.g. when query is copy-pasted $search = trim($search); /** @var ISearchResult $searchResult */ - $searchResult = $this->c->resolve(SearchResult::class); + $searchResult = $this->container->resolve(SearchResult::class); foreach ($shareTypes as $type) { if (!isset($this->pluginList[$type])) { @@ -68,14 +43,14 @@ class Search implements ISearch { } foreach ($this->pluginList[$type] as $plugin) { /** @var ISearchPlugin $searchPlugin */ - $searchPlugin = $this->c->resolve($plugin); + $searchPlugin = $this->container->resolve($plugin); $hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults; } } // Get from lookup server, not a separate share type if ($lookup) { - $searchPlugin = $this->c->resolve(LookupPlugin::class); + $searchPlugin = $this->container->resolve(LookupPlugin::class); $hasMoreResults = $searchPlugin->search($search, $limit, $offset, $searchResult) || $hasMoreResults; } @@ -97,7 +72,7 @@ class Search implements ISearch { // if we have an exact local user match with an email-a-like query, // there is no need to show the remote and email matches. $userType = new SearchResultType('users'); - if (strpos($search, '@') !== false && $searchResult->hasExactIdMatch($userType)) { + if (str_contains($search, '@') && $searchResult->hasExactIdMatch($userType)) { $searchResult->unsetResult($remoteType); $searchResult->unsetResult($emailType); } @@ -105,8 +80,8 @@ class Search implements ISearch { return [$searchResult->asArray(), $hasMoreResults]; } - public function registerPlugin(array $pluginInfo) { - $shareType = constant(Share::class . '::' . $pluginInfo['shareType']); + public function registerPlugin(array $pluginInfo): void { + $shareType = constant(IShare::class . '::' . substr($pluginInfo['shareType'], strlen('SHARE_'))); if ($shareType === null) { throw new \InvalidArgumentException('Provided ShareType is invalid'); } diff --git a/lib/private/Collaboration/Collaborators/SearchResult.php b/lib/private/Collaboration/Collaborators/SearchResult.php index 76d78c9c231..c9c2f032f36 100644 --- a/lib/private/Collaboration/Collaborators/SearchResult.php +++ b/lib/private/Collaboration/Collaborators/SearchResult.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -28,13 +10,13 @@ use OCP\Collaboration\Collaborators\ISearchResult; use OCP\Collaboration\Collaborators\SearchResultType; class SearchResult implements ISearchResult { - protected $result = [ + protected array $result = [ 'exact' => [], ]; - protected $exactIdMatches = []; + protected array $exactIdMatches = []; - public function addResultSet(SearchResultType $type, array $matches, array $exactMatches = null) { + public function addResultSet(SearchResultType $type, array $matches, ?array $exactMatches = null): void { $type = $type->getLabel(); if (!isset($this->result[$type])) { $this->result[$type] = []; @@ -47,15 +29,15 @@ class SearchResult implements ISearchResult { } } - public function markExactIdMatch(SearchResultType $type) { + public function markExactIdMatch(SearchResultType $type): void { $this->exactIdMatches[$type->getLabel()] = 1; } - public function hasExactIdMatch(SearchResultType $type) { + public function hasExactIdMatch(SearchResultType $type): bool { return isset($this->exactIdMatches[$type->getLabel()]); } - public function hasResult(SearchResultType $type, $collaboratorId) { + public function hasResult(SearchResultType $type, $collaboratorId): bool { $type = $type->getLabel(); if (!isset($this->result[$type])) { return false; @@ -73,11 +55,11 @@ class SearchResult implements ISearchResult { return false; } - public function asArray() { + public function asArray(): array { return $this->result; } - public function unsetResult(SearchResultType $type) { + public function unsetResult(SearchResultType $type): void { $type = $type->getLabel(); $this->result[$type] = []; if (isset($this->result['exact'][$type])) { diff --git a/lib/private/Collaboration/Collaborators/UserPlugin.php b/lib/private/Collaboration/Collaborators/UserPlugin.php index af4d8b2ccca..671181aea35 100644 --- a/lib/private/Collaboration/Collaborators/UserPlugin.php +++ b/lib/private/Collaboration/Collaborators/UserPlugin.php @@ -1,33 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Georg Ehrke <oc.list@georgehrke.com> - * @author Joas Schilling <coding@schilljs.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Citharel <nextcloud@tcit.fr> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Collaborators; @@ -44,70 +19,62 @@ use OCP\Share\IShare; use OCP\UserStatus\IManager as IUserStatusManager; class UserPlugin implements ISearchPlugin { - /* @var bool */ - protected $shareWithGroupOnly; - /* @var bool */ - protected $shareeEnumeration; - /* @var bool */ - protected $shareeEnumerationInGroupOnly; - /* @var bool */ - protected $shareeEnumerationPhone; - /* @var bool */ - protected $shareeEnumerationFullMatch; - /* @var bool */ - protected $shareeEnumerationFullMatchUserId; - /* @var bool */ - protected $shareeEnumerationFullMatchIgnoreSecondDisplayName; - - /** @var IConfig */ - private $config; - /** @var IGroupManager */ - private $groupManager; - /** @var IUserSession */ - private $userSession; - /** @var IUserManager */ - private $userManager; - /** @var KnownUserService */ - private $knownUserService; - /** @var IUserStatusManager */ - private $userStatusManager; - - public function __construct(IConfig $config, - IUserManager $userManager, - IGroupManager $groupManager, - IUserSession $userSession, - KnownUserService $knownUserService, - IUserStatusManager $userStatusManager) { - $this->config = $config; - - $this->groupManager = $groupManager; - $this->userSession = $userSession; - $this->userManager = $userManager; - $this->knownUserService = $knownUserService; - $this->userStatusManager = $userStatusManager; + protected bool $shareWithGroupOnly; + + protected bool $shareeEnumeration; + + protected bool $shareeEnumerationInGroupOnly; + + protected bool $shareeEnumerationPhone; + + protected bool $shareeEnumerationFullMatch; + + protected bool $shareeEnumerationFullMatchUserId; + + protected bool $shareeEnumerationFullMatchEmail; + protected bool $shareeEnumerationFullMatchIgnoreSecondDisplayName; + + public function __construct( + private IConfig $config, + private IUserManager $userManager, + private IGroupManager $groupManager, + private IUserSession $userSession, + private KnownUserService $knownUserService, + private IUserStatusManager $userStatusManager, + private mixed $shareWithGroupOnlyExcludeGroupsList = [], + ) { $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'; $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; $this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes'; $this->shareeEnumerationFullMatchUserId = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes') === 'yes'; - $this->shareeEnumerationFullMatchIgnoreSecondDisplayName = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_display_name', 'no') === 'yes'; + $this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes'; + $this->shareeEnumerationFullMatchIgnoreSecondDisplayName = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes'; + + if ($this->shareWithGroupOnly) { + $this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? []; + } } - public function search($search, $limit, $offset, ISearchResult $searchResult) { + public function search($search, $limit, $offset, ISearchResult $searchResult): bool { $result = ['wide' => [], 'exact' => []]; $users = []; $hasMoreResults = false; $currentUserId = $this->userSession->getUser()->getUID(); $currentUserGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser()); + + // ShareWithGroupOnly filtering + $currentUserGroups = array_diff($currentUserGroups, $this->shareWithGroupOnlyExcludeGroupsList); + if ($this->shareWithGroupOnly || $this->shareeEnumerationInGroupOnly) { // Search in all the groups this user is part of foreach ($currentUserGroups as $userGroupId) { $usersInGroup = $this->groupManager->displayNamesInGroup($userGroupId, $search, $limit, $offset); foreach ($usersInGroup as $userId => $displayName) { - $userId = (string) $userId; + $userId = (string)$userId; $user = $this->userManager->get($userId); if (!$user->isEnabled()) { // Ignore disabled users @@ -164,7 +131,7 @@ class UserPlugin implements ISearchPlugin { foreach ($users as $uid => $user) { $userDisplayName = $user->getDisplayName(); $userEmail = $user->getSystemEMailAddress(); - $uid = (string) $uid; + $uid = (string)$uid; $status = []; if (array_key_exists($uid, $userStatuses)) { @@ -181,11 +148,11 @@ class UserPlugin implements ISearchPlugin { if ( - $this->shareeEnumerationFullMatch && - $lowerSearch !== '' && (strtolower($uid) === $lowerSearch || - strtolower($userDisplayName) === $lowerSearch || - ($this->shareeEnumerationFullMatchIgnoreSecondDisplayName && trim(strtolower(preg_replace('/ \(.*\)$/', '', $userDisplayName))) === $lowerSearch) || - strtolower($userEmail ?? '') === $lowerSearch) + $this->shareeEnumerationFullMatch + && $lowerSearch !== '' && (strtolower($uid) === $lowerSearch + || strtolower($userDisplayName) === $lowerSearch + || ($this->shareeEnumerationFullMatchIgnoreSecondDisplayName && trim(strtolower(preg_replace('/ \(.*\)$/', '', $userDisplayName))) === $lowerSearch) + || ($this->shareeEnumerationFullMatchEmail && strtolower($userEmail ?? '') === $lowerSearch)) ) { if (strtolower($uid) === $lowerSearch) { $foundUserById = true; @@ -203,8 +170,8 @@ class UserPlugin implements ISearchPlugin { ]; } else { $addToWideResults = false; - if ($this->shareeEnumeration && - !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone)) { + if ($this->shareeEnumeration + && !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone)) { $addToWideResults = true; } @@ -279,8 +246,6 @@ class UserPlugin implements ISearchPlugin { } } - - $type = new SearchResultType('users'); $searchResult->addResultSet($type, $result['wide'], $result['exact']); if (count($result['exact'])) { @@ -290,7 +255,7 @@ class UserPlugin implements ISearchPlugin { return $hasMoreResults; } - public function takeOutCurrentUser(array &$users) { + public function takeOutCurrentUser(array &$users): void { $currentUser = $this->userSession->getUser(); if (!is_null($currentUser)) { if (isset($users[$currentUser->getUID()])) { diff --git a/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php b/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php new file mode 100644 index 00000000000..9c18531c8e7 --- /dev/null +++ b/lib/private/Collaboration/Reference/File/FileReferenceEventListener.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Collaboration\Reference\File; + +use OC\Files\Node\NonExistingFile; +use OC\Files\Node\NonExistingFolder; +use OCP\Collaboration\Reference\IReferenceManager; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\EventDispatcher\IEventListener; +use OCP\Files\Events\Node\NodeDeletedEvent; +use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Share\Events\ShareCreatedEvent; +use OCP\Share\Events\ShareDeletedEvent; + +/** @template-implements IEventListener<Event|NodeDeletedEvent|ShareDeletedEvent|ShareCreatedEvent> */ +class FileReferenceEventListener implements IEventListener { + public function __construct( + private IReferenceManager $manager, + ) { + } + + public static function register(IEventDispatcher $eventDispatcher): void { + $eventDispatcher->addServiceListener(NodeDeletedEvent::class, FileReferenceEventListener::class); + $eventDispatcher->addServiceListener(NodeRenamedEvent::class, FileReferenceEventListener::class); + $eventDispatcher->addServiceListener(ShareDeletedEvent::class, FileReferenceEventListener::class); + $eventDispatcher->addServiceListener(ShareCreatedEvent::class, FileReferenceEventListener::class); + } + + /** + * @inheritDoc + */ + public function handle(Event $event): void { + if ($event instanceof NodeDeletedEvent) { + if ($event->getNode() instanceof NonExistingFolder || $event->getNode() instanceof NonExistingFile) { + return; + } + + $this->manager->invalidateCache((string)$event->getNode()->getId()); + } + if ($event instanceof NodeRenamedEvent) { + $this->manager->invalidateCache((string)$event->getTarget()->getId()); + } + if ($event instanceof ShareDeletedEvent) { + $this->manager->invalidateCache((string)$event->getShare()->getNodeId()); + } + if ($event instanceof ShareCreatedEvent) { + $this->manager->invalidateCache((string)$event->getShare()->getNodeId()); + } + } +} diff --git a/lib/private/Collaboration/Reference/File/FileReferenceProvider.php b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php new file mode 100644 index 00000000000..3cb174d9607 --- /dev/null +++ b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php @@ -0,0 +1,161 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Collaboration\Reference\File; + +use OC\User\NoUserException; +use OCP\Collaboration\Reference\ADiscoverableReferenceProvider; +use OCP\Collaboration\Reference\IReference; +use OCP\Collaboration\Reference\Reference; +use OCP\Files\IMimeTypeDetector; +use OCP\Files\InvalidPathException; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IL10N; +use OCP\IPreview; +use OCP\IURLGenerator; +use OCP\IUserSession; +use OCP\L10N\IFactory; + +class FileReferenceProvider extends ADiscoverableReferenceProvider { + private ?string $userId; + private IL10N $l10n; + + public function __construct( + private IURLGenerator $urlGenerator, + private IRootFolder $rootFolder, + IUserSession $userSession, + private IMimeTypeDetector $mimeTypeDetector, + private IPreview $previewManager, + IFactory $l10n, + ) { + $this->userId = $userSession->getUser()?->getUID(); + $this->l10n = $l10n->get('files'); + } + + public function matchReference(string $referenceText): bool { + return $this->getFilesAppLinkId($referenceText) !== null; + } + + private function getFilesAppLinkId(string $referenceText): ?int { + $start = $this->urlGenerator->getAbsoluteURL('/apps/files/'); + $startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/files/'); + + $fileId = null; + + if (mb_strpos($referenceText, $start) === 0) { + $parts = parse_url($referenceText); + parse_str($parts['query'] ?? '', $query); + $fileId = isset($query['fileid']) ? (int)$query['fileid'] : $fileId; + $fileId = isset($query['openfile']) ? (int)$query['openfile'] : $fileId; + } + + if (mb_strpos($referenceText, $startIndex) === 0) { + $parts = parse_url($referenceText); + parse_str($parts['query'] ?? '', $query); + $fileId = isset($query['fileid']) ? (int)$query['fileid'] : $fileId; + $fileId = isset($query['openfile']) ? (int)$query['openfile'] : $fileId; + } + + if (mb_strpos($referenceText, $this->urlGenerator->getAbsoluteURL('/index.php/f/')) === 0) { + $fileId = str_replace($this->urlGenerator->getAbsoluteURL('/index.php/f/'), '', $referenceText); + } + + if (mb_strpos($referenceText, $this->urlGenerator->getAbsoluteURL('/f/')) === 0) { + $fileId = str_replace($this->urlGenerator->getAbsoluteURL('/f/'), '', $referenceText); + } + + return $fileId !== null ? (int)$fileId : null; + } + + public function resolveReference(string $referenceText): ?IReference { + if ($this->matchReference($referenceText)) { + $reference = new Reference($referenceText); + try { + $this->fetchReference($reference); + } catch (NotFoundException $e) { + $reference->setRichObject('file', null); + $reference->setAccessible(false); + } + return $reference; + } + + return null; + } + + /** + * @throws NotFoundException + */ + private function fetchReference(Reference $reference): void { + if ($this->userId === null) { + throw new NotFoundException(); + } + + $fileId = $this->getFilesAppLinkId($reference->getId()); + if ($fileId === null) { + throw new NotFoundException(); + } + + try { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + $file = $userFolder->getFirstNodeById($fileId); + + if (!$file) { + throw new NotFoundException(); + } + + $reference->setTitle($file->getName()); + $reference->setDescription($file->getMimetype()); + $reference->setUrl($this->urlGenerator->getAbsoluteURL('/index.php/f/' . $fileId)); + if ($this->previewManager->isMimeSupported($file->getMimeType())) { + $reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute('core.Preview.getPreviewByFileId', ['x' => 1600, 'y' => 630, 'fileId' => $fileId])); + } else { + $fileTypeIconUrl = $this->mimeTypeDetector->mimeTypeIcon($file->getMimeType()); + $reference->setImageUrl($fileTypeIconUrl); + } + + $reference->setRichObject('file', [ + 'id' => $file->getId(), + 'name' => $file->getName(), + 'size' => $file->getSize(), + 'path' => $userFolder->getRelativePath($file->getPath()), + 'link' => $reference->getUrl(), + 'mimetype' => $file->getMimetype(), + 'mtime' => $file->getMTime(), + 'preview-available' => $this->previewManager->isAvailable($file) + ]); + } catch (InvalidPathException|NotFoundException|NotPermittedException|NoUserException $e) { + throw new NotFoundException(); + } + } + + public function getCachePrefix(string $referenceId): string { + return (string)$this->getFilesAppLinkId($referenceId); + } + + public function getCacheKey(string $referenceId): ?string { + return $this->userId ?? ''; + } + + public function getId(): string { + return 'files'; + } + + public function getTitle(): string { + return $this->l10n->t('Files'); + } + + public function getOrder(): int { + return 0; + } + + public function getIconUrl(): string { + return $this->urlGenerator->imagePath('files', 'folder.svg'); + } +} diff --git a/lib/private/Collaboration/Reference/LinkReferenceProvider.php b/lib/private/Collaboration/Reference/LinkReferenceProvider.php new file mode 100644 index 00000000000..5af23bf633d --- /dev/null +++ b/lib/private/Collaboration/Reference/LinkReferenceProvider.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Collaboration\Reference; + +use OCP\Collaboration\Reference\LinkReferenceProvider as OCPLinkReferenceProvider; + +/** @deprecated 29.0.0 Use OCP\Collaboration\Reference\LinkReferenceProvider instead */ +class LinkReferenceProvider extends OCPLinkReferenceProvider { +} diff --git a/lib/private/Collaboration/Reference/ReferenceManager.php b/lib/private/Collaboration/Reference/ReferenceManager.php new file mode 100644 index 00000000000..9287b66b2a2 --- /dev/null +++ b/lib/private/Collaboration/Reference/ReferenceManager.php @@ -0,0 +1,262 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Collaboration\Reference; + +use OC\AppFramework\Bootstrap\Coordinator; +use OC\Collaboration\Reference\File\FileReferenceProvider; +use OCP\Collaboration\Reference\IDiscoverableReferenceProvider; +use OCP\Collaboration\Reference\IPublicReferenceProvider; +use OCP\Collaboration\Reference\IReference; +use OCP\Collaboration\Reference\IReferenceManager; +use OCP\Collaboration\Reference\IReferenceProvider; +use OCP\Collaboration\Reference\Reference; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\IUserSession; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Throwable; + +class ReferenceManager implements IReferenceManager { + public const CACHE_TTL = 3600; + + /** @var IReferenceProvider[]|null */ + private ?array $providers = null; + private ICache $cache; + + public function __construct( + private LinkReferenceProvider $linkReferenceProvider, + ICacheFactory $cacheFactory, + private Coordinator $coordinator, + private ContainerInterface $container, + private LoggerInterface $logger, + private IConfig $config, + private IUserSession $userSession, + ) { + $this->cache = $cacheFactory->createDistributed('reference'); + } + + /** + * Extract a list of URLs from a text + * + * @return string[] + */ + public function extractReferences(string $text): array { + preg_match_all(IURLGenerator::URL_REGEX, $text, $matches); + $references = $matches[0] ?? []; + return array_map(function ($reference) { + return trim($reference); + }, $references); + } + + /** + * Try to get a cached reference object from a reference string + */ + public function getReferenceFromCache(string $referenceId, bool $public = false, string $sharingToken = ''): ?IReference { + $matchedProvider = $this->getMatchedProvider($referenceId, $public); + + if ($matchedProvider === null) { + return null; + } + + $cacheKey = $this->getFullCacheKey($matchedProvider, $referenceId, $public, $sharingToken); + return $this->getReferenceByCacheKey($cacheKey); + } + + /** + * Try to get a cached reference object from a full cache key + */ + public function getReferenceByCacheKey(string $cacheKey): ?IReference { + $cached = $this->cache->get($cacheKey); + if ($cached) { + return Reference::fromCache($cached); + } + + return null; + } + + /** + * Get a reference object from a reference string with a matching provider + * Use a cached reference if possible + */ + public function resolveReference(string $referenceId, bool $public = false, $sharingToken = ''): ?IReference { + $matchedProvider = $this->getMatchedProvider($referenceId, $public); + + if ($matchedProvider === null) { + return null; + } + + $cacheKey = $this->getFullCacheKey($matchedProvider, $referenceId, $public, $sharingToken); + $cached = $this->cache->get($cacheKey); + if ($cached) { + return Reference::fromCache($cached); + } + + $reference = null; + if ($public && $matchedProvider instanceof IPublicReferenceProvider) { + $reference = $matchedProvider->resolveReferencePublic($referenceId, $sharingToken); + } elseif ($matchedProvider instanceof IReferenceProvider) { + $reference = $matchedProvider->resolveReference($referenceId); + } + if ($reference) { + $cachePrefix = $matchedProvider->getCachePrefix($referenceId); + if ($cachePrefix !== '') { + // If a prefix is used we set an additional key to know when we need to delete by prefix during invalidateCache() + $this->cache->set('hasPrefix-' . md5($cachePrefix), true, self::CACHE_TTL); + } + $this->cache->set($cacheKey, Reference::toCache($reference), self::CACHE_TTL); + return $reference; + } + + return null; + } + + /** + * Try to match a reference string with all the registered providers + * Fallback to the link reference provider (using OpenGraph) + * + * @return IReferenceProvider|IPublicReferenceProvider|null the first matching provider + */ + private function getMatchedProvider(string $referenceId, bool $public): null|IReferenceProvider|IPublicReferenceProvider { + $matchedProvider = null; + foreach ($this->getProviders() as $provider) { + if ($public && !($provider instanceof IPublicReferenceProvider)) { + continue; + } + $matchedProvider = $provider->matchReference($referenceId) ? $provider : null; + if ($matchedProvider !== null) { + break; + } + } + + if ($matchedProvider === null && $this->linkReferenceProvider->matchReference($referenceId)) { + $matchedProvider = $this->linkReferenceProvider; + } + + return $matchedProvider; + } + + /** + * Get a hashed full cache key from a key and prefix given by a provider + */ + private function getFullCacheKey(IReferenceProvider $provider, string $referenceId, bool $public, string $sharingToken): string { + if ($public && !($provider instanceof IPublicReferenceProvider)) { + throw new \RuntimeException('Provider doesn\'t support public lookups'); + } + $cacheKey = $public + ? $provider->getCacheKeyPublic($referenceId, $sharingToken) + : $provider->getCacheKey($referenceId); + return md5($provider->getCachePrefix($referenceId)) . ( + $cacheKey !== null ? ('-' . md5($cacheKey)) : '' + ); + } + + /** + * Remove a specific cache entry from its key+prefix + */ + public function invalidateCache(string $cachePrefix, ?string $cacheKey = null): void { + if ($cacheKey === null) { + // clear might be a heavy operation, so we only do it if there have actually been keys set + if ($this->cache->remove('hasPrefix-' . md5($cachePrefix))) { + $this->cache->clear(md5($cachePrefix)); + } + + return; + } + + $this->cache->remove(md5($cachePrefix) . '-' . md5($cacheKey)); + } + + /** + * @return IReferenceProvider[] + */ + public function getProviders(): array { + if ($this->providers === null) { + $context = $this->coordinator->getRegistrationContext(); + if ($context === null) { + return []; + } + + $this->providers = array_filter(array_map(function ($registration): ?IReferenceProvider { + try { + /** @var IReferenceProvider $provider */ + $provider = $this->container->get($registration->getService()); + } catch (Throwable $e) { + $this->logger->error('Could not load reference provider ' . $registration->getService() . ': ' . $e->getMessage(), [ + 'exception' => $e, + ]); + return null; + } + + return $provider; + }, $context->getReferenceProviders())); + + $this->providers[] = $this->container->get(FileReferenceProvider::class); + } + + return $this->providers; + } + + /** + * @inheritDoc + */ + public function getDiscoverableProviders(): array { + // preserve 0 based index to avoid returning an object in data responses + return array_values( + array_filter($this->getProviders(), static function (IReferenceProvider $provider) { + return $provider instanceof IDiscoverableReferenceProvider; + }) + ); + } + + /** + * @inheritDoc + */ + public function touchProvider(string $userId, string $providerId, ?int $timestamp = null): bool { + $providers = $this->getDiscoverableProviders(); + $matchingProviders = array_filter($providers, static function (IDiscoverableReferenceProvider $provider) use ($providerId) { + return $provider->getId() === $providerId; + }); + if (!empty($matchingProviders)) { + if ($timestamp === null) { + $timestamp = time(); + } + + $configKey = 'provider-last-use_' . $providerId; + $this->config->setUserValue($userId, 'references', $configKey, (string)$timestamp); + return true; + } + return false; + } + + /** + * @inheritDoc + */ + public function getUserProviderTimestamps(): array { + $user = $this->userSession->getUser(); + if ($user === null) { + return []; + } + $userId = $user->getUID(); + $keys = $this->config->getUserKeys($userId, 'references'); + $prefix = 'provider-last-use_'; + $keys = array_filter($keys, static function (string $key) use ($prefix) { + return str_starts_with($key, $prefix); + }); + $timestamps = []; + foreach ($keys as $key) { + $providerId = substr($key, strlen($prefix)); + $timestamp = (int)$this->config->getUserValue($userId, 'references', $key); + $timestamps[$providerId] = $timestamp; + } + return $timestamps; + } +} diff --git a/lib/private/Collaboration/Reference/RenderReferenceEventListener.php b/lib/private/Collaboration/Reference/RenderReferenceEventListener.php new file mode 100644 index 00000000000..9e6192314cb --- /dev/null +++ b/lib/private/Collaboration/Reference/RenderReferenceEventListener.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Collaboration\Reference; + +use OCP\Collaboration\Reference\IDiscoverableReferenceProvider; +use OCP\Collaboration\Reference\IReferenceManager; +use OCP\Collaboration\Reference\RenderReferenceEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\EventDispatcher\IEventListener; +use OCP\IInitialStateService; + +/** @template-implements IEventListener<Event|RenderReferenceEvent> */ +class RenderReferenceEventListener implements IEventListener { + public function __construct( + private IReferenceManager $manager, + private IInitialStateService $initialStateService, + ) { + } + + public static function register(IEventDispatcher $eventDispatcher): void { + $eventDispatcher->addServiceListener(RenderReferenceEvent::class, RenderReferenceEventListener::class); + } + + /** + * @inheritDoc + */ + public function handle(Event $event): void { + if (!($event instanceof RenderReferenceEvent)) { + return; + } + + $providers = $this->manager->getDiscoverableProviders(); + $jsonProviders = array_map(static function (IDiscoverableReferenceProvider $provider) { + return $provider->jsonSerialize(); + }, $providers); + $this->initialStateService->provideInitialState('core', 'reference-provider-list', $jsonProviders); + + $timestamps = $this->manager->getUserProviderTimestamps(); + $this->initialStateService->provideInitialState('core', 'reference-provider-timestamps', $timestamps); + } +} diff --git a/lib/private/Collaboration/Resources/Collection.php b/lib/private/Collaboration/Resources/Collection.php index ba4005c2139..2481a3e9a09 100644 --- a/lib/private/Collaboration/Resources/Collection.php +++ b/lib/private/Collaboration/Resources/Collection.php @@ -3,27 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Resources; @@ -37,47 +18,21 @@ use OCP\IDBConnection; use OCP\IUser; class Collection implements ICollection { - - /** @var IManager|Manager */ - protected $manager; - - /** @var IDBConnection */ - protected $connection; - - /** @var int */ - protected $id; - - /** @var string */ - protected $name; - - /** @var IUser|null */ - protected $userForAccess; - - /** @var bool|null */ - protected $access; - /** @var IResource[] */ - protected $resources; + protected array $resources = []; public function __construct( - IManager $manager, - IDBConnection $connection, - int $id, - string $name, - ?IUser $userForAccess = null, - ?bool $access = null + /** @var Manager $manager */ + protected IManager $manager, + protected IDBConnection $connection, + protected int $id, + protected string $name, + protected ?IUser $userForAccess = null, + protected ?bool $access = null, ) { - $this->manager = $manager; - $this->connection = $connection; - $this->id = $id; - $this->name = $name; - $this->userForAccess = $userForAccess; - $this->access = $access; - $this->resources = []; } /** - * @return int * @since 16.0.0 */ public function getId(): int { @@ -85,7 +40,6 @@ class Collection implements ICollection { } /** - * @return string * @since 16.0.0 */ public function getName(): string { @@ -93,7 +47,6 @@ class Collection implements ICollection { } /** - * @param string $name * @since 16.0.0 */ public function setName(string $name): void { @@ -101,7 +54,7 @@ class Collection implements ICollection { $query->update(Manager::TABLE_COLLECTIONS) ->set('name', $query->createNamedParameter($name)) ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); $this->name = $name; } @@ -121,7 +74,6 @@ class Collection implements ICollection { /** * Adds a resource to a collection * - * @param IResource $resource * @throws ResourceException when the resource is already part of the collection * @since 16.0.0 */ @@ -154,7 +106,6 @@ class Collection implements ICollection { /** * Removes a resource from a collection * - * @param IResource $resource * @since 16.0.0 */ public function removeResource(IResource $resource): void { @@ -167,7 +118,7 @@ class Collection implements ICollection { ->where($query->expr()->eq('collection_id', $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->eq('resource_type', $query->createNamedParameter($resource->getType()))) ->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId()))); - $query->execute(); + $query->executeStatement(); if (empty($this->resources)) { $this->removeCollection(); @@ -179,8 +130,6 @@ class Collection implements ICollection { /** * Can a user/guest access the collection * - * @param IUser|null $user - * @return bool * @since 16.0.0 */ public function canAccess(?IUser $user): bool { @@ -215,15 +164,15 @@ class Collection implements ICollection { } protected function isSameResource(IResource $resource1, IResource $resource2): bool { - return $resource1->getType() === $resource2->getType() && - $resource1->getId() === $resource2->getId(); + return $resource1->getType() === $resource2->getType() + && $resource1->getId() === $resource2->getId(); } protected function removeCollection(): void { $query = $this->connection->getQueryBuilder(); $query->delete(Manager::TABLE_COLLECTIONS) ->where($query->expr()->eq('id', $query->createNamedParameter($this->id, IQueryBuilder::PARAM_INT))); - $query->execute(); + $query->executeStatement(); $this->manager->invalidateAccessCacheForCollection($this); $this->id = 0; diff --git a/lib/private/Collaboration/Resources/Listener.php b/lib/private/Collaboration/Resources/Listener.php index ba012b4ca44..dfdde24d78e 100644 --- a/lib/private/Collaboration/Resources/Listener.php +++ b/lib/private/Collaboration/Resources/Listener.php @@ -3,62 +3,47 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Resources; use OCP\Collaboration\Resources\IManager; -use OCP\IGroup; -use OCP\IUser; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\GenericEvent; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Group\Events\BeforeGroupDeletedEvent; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; +use OCP\User\Events\UserDeletedEvent; class Listener { - public static function register(EventDispatcherInterface $dispatcher): void { - $listener = function (GenericEvent $event) { - /** @var IUser $user */ - $user = $event->getArgument('user'); + public static function register(IEventDispatcher $eventDispatcher): void { + $eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) { + $user = $event->getUser(); /** @var IManager $resourceManager */ - $resourceManager = \OC::$server->query(IManager::class); + $resourceManager = \OCP\Server::get(IManager::class); $resourceManager->invalidateAccessCacheForUser($user); - }; - $dispatcher->addListener(IGroup::class . '::postAddUser', $listener); - $dispatcher->addListener(IGroup::class . '::postRemoveUser', $listener); + }); + $eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) { + $user = $event->getUser(); + /** @var IManager $resourceManager */ + $resourceManager = \OCP\Server::get(IManager::class); + + $resourceManager->invalidateAccessCacheForUser($user); + }); - $dispatcher->addListener(IUser::class . '::postDelete', function (GenericEvent $event) { - /** @var IUser $user */ - $user = $event->getSubject(); + $eventDispatcher->addListener(UserDeletedEvent::class, function (UserDeletedEvent $event) { + $user = $event->getUser(); /** @var IManager $resourceManager */ - $resourceManager = \OC::$server->query(IManager::class); + $resourceManager = \OCP\Server::get(IManager::class); $resourceManager->invalidateAccessCacheForUser($user); }); - $dispatcher->addListener(IGroup::class . '::preDelete', function (GenericEvent $event) { - /** @var IGroup $group */ - $group = $event->getSubject(); + $eventDispatcher->addListener(BeforeGroupDeletedEvent::class, function (BeforeGroupDeletedEvent $event) { + $group = $event->getGroup(); /** @var IManager $resourceManager */ - $resourceManager = \OC::$server->query(IManager::class); + $resourceManager = \OCP\Server::get(IManager::class); foreach ($group->getUsers() as $user) { $resourceManager->invalidateAccessCacheForUser($user); diff --git a/lib/private/Collaboration/Resources/Manager.php b/lib/private/Collaboration/Resources/Manager.php index 8fc3d0dd5ea..8d1e4b13287 100644 --- a/lib/private/Collaboration/Resources/Manager.php +++ b/lib/private/Collaboration/Resources/Manager.php @@ -3,27 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Resources; @@ -45,26 +26,17 @@ class Manager implements IManager { public const TABLE_RESOURCES = 'collres_resources'; public const TABLE_ACCESS_CACHE = 'collres_accesscache'; - /** @var IDBConnection */ - protected $connection; - /** @var IProviderManager */ - protected $providerManager; - /** @var LoggerInterface */ - protected $logger; - /** @var string[] */ - protected $providers = []; - + protected array $providers = []; - public function __construct(IDBConnection $connection, IProviderManager $providerManager, LoggerInterface $logger) { - $this->connection = $connection; - $this->providerManager = $providerManager; - $this->logger = $logger; + public function __construct( + protected IDBConnection $connection, + protected IProviderManager $providerManager, + protected LoggerInterface $logger, + ) { } /** - * @param int $id - * @return ICollection * @throws CollectionException when the collection could not be found * @since 16.0.0 */ @@ -81,13 +53,10 @@ class Manager implements IManager { throw new CollectionException('Collection not found'); } - return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name']); + return new Collection($this, $this->connection, (int)$row['id'], (string)$row['name']); } /** - * @param int $id - * @param IUser|null $user - * @return ICollection * @throws CollectionException when the collection could not be found * @since 16.0.0 */ @@ -113,19 +82,15 @@ class Manager implements IManager { throw new CollectionException('Collection not found'); } - $access = $row['access'] === null ? null : (bool) $row['access']; + $access = $row['access'] === null ? null : (bool)$row['access']; if ($user instanceof IUser) { - return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access); + return new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access); } - return new Collection($this, $this->connection, (int) $row['id'], (string) $row['name'], $user, $access); + return new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access); } /** - * @param IUser $user - * @param string $filter - * @param int $limit - * @param int $start * @return ICollection[] * @since 16.0.0 */ @@ -148,7 +113,7 @@ class Manager implements IManager { ->setFirstResult($start); if ($filter !== '') { - $query->where($query->expr()->iLike('c.name', $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($filter) . '%'))); + $query->andWhere($query->expr()->iLike('c.name', $query->createNamedParameter('%' . $this->connection->escapeLikeParameter($filter) . '%'))); } $result = $query->execute(); @@ -157,7 +122,7 @@ class Manager implements IManager { $foundResults = 0; while ($row = $result->fetch()) { $foundResults++; - $access = $row['access'] === null ? null : (bool) $row['access']; + $access = $row['access'] === null ? null : (bool)$row['access']; $collection = new Collection($this, $this->connection, (int)$row['id'], (string)$row['name'], $user, $access); if ($collection->canAccess($user)) { $collections[] = $collection; @@ -173,8 +138,6 @@ class Manager implements IManager { } /** - * @param string $name - * @return ICollection * @since 16.0.0 */ public function newCollection(string $name): ICollection { @@ -189,9 +152,6 @@ class Manager implements IManager { } /** - * @param string $type - * @param string $id - * @return IResource * @since 16.0.0 */ public function createResource(string $type, string $id): IResource { @@ -199,10 +159,6 @@ class Manager implements IManager { } /** - * @param string $type - * @param string $id - * @param IUser|null $user - * @return IResource * @throws ResourceException * @since 16.0.0 */ @@ -230,7 +186,7 @@ class Manager implements IManager { throw new ResourceException('Resource not found'); } - $access = $row['access'] === null ? null : (bool) $row['access']; + $access = $row['access'] === null ? null : (bool)$row['access']; if ($user instanceof IUser) { return new Resource($this, $this->connection, $type, $id, $user, $access); } @@ -239,8 +195,6 @@ class Manager implements IManager { } /** - * @param ICollection $collection - * @param IUser|null $user * @return IResource[] * @since 16.0.0 */ @@ -263,7 +217,7 @@ class Manager implements IManager { $resources = []; $result = $query->execute(); while ($row = $result->fetch()) { - $access = $row['access'] === null ? null : (bool) $row['access']; + $access = $row['access'] === null ? null : (bool)$row['access']; $resources[] = new Resource($this, $this->connection, $row['resource_type'], $row['resource_id'], $user, $access); } $result->closeCursor(); @@ -274,8 +228,6 @@ class Manager implements IManager { /** * Get the rich object data of a resource * - * @param IResource $resource - * @return array * @since 16.0.0 */ public function getResourceRichObject(IResource $resource): array { @@ -294,9 +246,6 @@ class Manager implements IManager { /** * Can a user/guest access the collection * - * @param IResource $resource - * @param IUser|null $user - * @return bool * @since 16.0.0 */ public function canAccessResource(IResource $resource, ?IUser $user): bool { @@ -325,9 +274,6 @@ class Manager implements IManager { /** * Can a user/guest access the collection * - * @param ICollection $collection - * @param IUser|null $user - * @return bool * @since 16.0.0 */ public function canAccessCollection(ICollection $collection, ?IUser $user): bool { @@ -365,7 +311,7 @@ class Manager implements IManager { $hasAccess = null; $result = $query->execute(); if ($row = $result->fetch()) { - $hasAccess = (bool) $row['access']; + $hasAccess = (bool)$row['access']; } $result->closeCursor(); @@ -385,7 +331,7 @@ class Manager implements IManager { $hasAccess = null; $result = $query->execute(); if ($row = $result->fetch()) { - $hasAccess = (bool) $row['access']; + $hasAccess = (bool)$row['access']; } $result->closeCursor(); @@ -505,9 +451,6 @@ class Manager implements IManager { $query->execute(); } - /** - * @param string $provider - */ public function registerResourceProvider(string $provider): void { $this->logger->debug('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated', ['provider' => $provider]); $this->providerManager->registerResourceProvider($provider); @@ -516,7 +459,6 @@ class Manager implements IManager { /** * Get the resource type of the provider * - * @return string * @since 16.0.0 */ public function getType(): string { diff --git a/lib/private/Collaboration/Resources/ProviderManager.php b/lib/private/Collaboration/Resources/ProviderManager.php index 4b099f33b4f..0ce4ae7155a 100644 --- a/lib/private/Collaboration/Resources/ProviderManager.php +++ b/lib/private/Collaboration/Resources/ProviderManager.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2019 Daniel Kesselberg <mail@danielkesselberg.de> - * - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Joas Schilling <coding@schilljs.com> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Resources; @@ -33,22 +15,16 @@ use OCP\IServerContainer; use Psr\Log\LoggerInterface; class ProviderManager implements IProviderManager { - /** @var string[] */ - protected $providers = []; + protected array $providers = []; /** @var IProvider[] */ - protected $providerInstances = []; - - /** @var IServerContainer */ - protected $serverContainer; - - /** @var LoggerInterface */ - protected $logger; + protected array $providerInstances = []; - public function __construct(IServerContainer $serverContainer, LoggerInterface $logger) { - $this->serverContainer = $serverContainer; - $this->logger = $logger; + public function __construct( + protected IServerContainer $serverContainer, + protected LoggerInterface $logger, + ) { } public function getResourceProviders(): array { diff --git a/lib/private/Collaboration/Resources/Resource.php b/lib/private/Collaboration/Resources/Resource.php index f138204403c..19da3da7e7d 100644 --- a/lib/private/Collaboration/Resources/Resource.php +++ b/lib/private/Collaboration/Resources/Resource.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com> - * - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Collaboration\Resources; @@ -33,46 +15,19 @@ use OCP\IDBConnection; use OCP\IUser; class Resource implements IResource { - - /** @var IManager */ - protected $manager; - - /** @var IDBConnection */ - protected $connection; - - /** @var string */ - protected $type; - - /** @var string */ - protected $id; - - /** @var IUser|null */ - protected $userForAccess; - - /** @var bool|null */ - protected $access; - - /** @var array|null */ - protected $data; + protected ?array $data = null; public function __construct( - IManager $manager, - IDBConnection $connection, - string $type, - string $id, - ?IUser $userForAccess = null, - ?bool $access = null + protected IManager $manager, + protected IDBConnection $connection, + protected string $type, + protected string $id, + protected ?IUser $userForAccess = null, + protected ?bool $access = null, ) { - $this->manager = $manager; - $this->connection = $connection; - $this->type = $type; - $this->id = $id; - $this->userForAccess = $userForAccess; - $this->access = $access; } /** - * @return string * @since 16.0.0 */ public function getType(): string { @@ -80,7 +35,6 @@ class Resource implements IResource { } /** - * @return string * @since 16.0.0 */ public function getId(): string { @@ -88,7 +42,6 @@ class Resource implements IResource { } /** - * @return array * @since 16.0.0 */ public function getRichObject(): array { @@ -102,8 +55,6 @@ class Resource implements IResource { /** * Can a user/guest access the resource * - * @param IUser|null $user - * @return bool * @since 16.0.0 */ public function canAccess(?IUser $user): bool { @@ -153,7 +104,7 @@ class Resource implements IResource { $result = $query->execute(); while ($row = $result->fetch()) { - $collections[] = $this->manager->getCollection((int) $row['collection_id']); + $collections[] = $this->manager->getCollection((int)$row['collection_id']); } $result->closeCursor(); |