RemotePlugin.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OC\Collaboration\Collaborators;
  7. use OCP\Collaboration\Collaborators\ISearchPlugin;
  8. use OCP\Collaboration\Collaborators\ISearchResult;
  9. use OCP\Collaboration\Collaborators\SearchResultType;
  10. use OCP\Contacts\IManager;
  11. use OCP\Federation\ICloudIdManager;
  12. use OCP\IConfig;
  13. use OCP\IUserManager;
  14. use OCP\IUserSession;
  15. use OCP\Share\IShare;
  16. class RemotePlugin implements ISearchPlugin {
  17. protected bool $shareeEnumeration;
  18. private string $userId;
  19. public function __construct(
  20. private IManager $contactsManager,
  21. private ICloudIdManager $cloudIdManager,
  22. private IConfig $config,
  23. private IUserManager $userManager,
  24. IUserSession $userSession,
  25. ) {
  26. $this->userId = $userSession->getUser()?->getUID() ?? '';
  27. $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
  28. }
  29. public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
  30. $result = ['wide' => [], 'exact' => []];
  31. $resultType = new SearchResultType('remotes');
  32. // Search in contacts
  33. $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN'], [
  34. 'limit' => $limit,
  35. 'offset' => $offset,
  36. 'enumeration' => false,
  37. 'fullmatch' => false,
  38. ]);
  39. foreach ($addressBookContacts as $contact) {
  40. if (isset($contact['isLocalSystemBook'])) {
  41. continue;
  42. }
  43. if (isset($contact['CLOUD'])) {
  44. $cloudIds = $contact['CLOUD'];
  45. if (is_string($cloudIds)) {
  46. $cloudIds = [$cloudIds];
  47. }
  48. $lowerSearch = strtolower($search);
  49. foreach ($cloudIds as $cloudId) {
  50. $cloudIdType = '';
  51. if (\is_array($cloudId)) {
  52. $cloudIdData = $cloudId;
  53. $cloudId = $cloudIdData['value'];
  54. $cloudIdType = $cloudIdData['type'];
  55. }
  56. try {
  57. [$remoteUser, $serverUrl] = $this->splitUserRemote($cloudId);
  58. } catch (\InvalidArgumentException $e) {
  59. continue;
  60. }
  61. $localUser = $this->userManager->get($remoteUser);
  62. /**
  63. * Add local share if remote cloud id matches a local user ones
  64. */
  65. if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId()) {
  66. $result['wide'][] = [
  67. 'label' => $contact['FN'],
  68. 'uuid' => $contact['UID'],
  69. 'value' => [
  70. 'shareType' => IShare::TYPE_USER,
  71. 'shareWith' => $remoteUser
  72. ],
  73. 'shareWithDisplayNameUnique' => $contact['EMAIL'] !== null && $contact['EMAIL'] !== '' ? $contact['EMAIL'] : $contact['UID'],
  74. ];
  75. }
  76. if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) {
  77. if (strtolower($cloudId) === $lowerSearch) {
  78. $searchResult->markExactIdMatch($resultType);
  79. }
  80. $result['exact'][] = [
  81. 'label' => $contact['FN'] . " ($cloudId)",
  82. 'uuid' => $contact['UID'],
  83. 'name' => $contact['FN'],
  84. 'type' => $cloudIdType,
  85. 'value' => [
  86. 'shareType' => IShare::TYPE_REMOTE,
  87. 'shareWith' => $cloudId,
  88. 'server' => $serverUrl,
  89. ],
  90. ];
  91. } else {
  92. $result['wide'][] = [
  93. 'label' => $contact['FN'] . " ($cloudId)",
  94. 'uuid' => $contact['UID'],
  95. 'name' => $contact['FN'],
  96. 'type' => $cloudIdType,
  97. 'value' => [
  98. 'shareType' => IShare::TYPE_REMOTE,
  99. 'shareWith' => $cloudId,
  100. 'server' => $serverUrl,
  101. ],
  102. ];
  103. }
  104. }
  105. }
  106. }
  107. if (!$this->shareeEnumeration) {
  108. $result['wide'] = [];
  109. } else {
  110. $result['wide'] = array_slice($result['wide'], $offset, $limit);
  111. }
  112. /**
  113. * Add generic share with remote item for valid cloud ids that are not users of the local instance
  114. */
  115. if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) {
  116. try {
  117. [$remoteUser, $serverUrl] = $this->splitUserRemote($search);
  118. $localUser = $this->userManager->get($remoteUser);
  119. if ($localUser === null || $search !== $localUser->getCloudId()) {
  120. $result['exact'][] = [
  121. 'label' => $remoteUser . " ($serverUrl)",
  122. 'uuid' => $remoteUser,
  123. 'name' => $remoteUser,
  124. 'value' => [
  125. 'shareType' => IShare::TYPE_REMOTE,
  126. 'shareWith' => $search,
  127. 'server' => $serverUrl,
  128. ],
  129. ];
  130. }
  131. } catch (\InvalidArgumentException $e) {
  132. }
  133. }
  134. $searchResult->addResultSet($resultType, $result['wide'], $result['exact']);
  135. return true;
  136. }
  137. /**
  138. * split user and remote from federated cloud id
  139. *
  140. * @param string $address federated share address
  141. * @return array [user, remoteURL]
  142. * @throws \InvalidArgumentException
  143. */
  144. public function splitUserRemote(string $address): array {
  145. try {
  146. $cloudId = $this->cloudIdManager->resolveCloudId($address);
  147. return [$cloudId->getUser(), $cloudId->getRemote()];
  148. } catch (\InvalidArgumentException $e) {
  149. throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e);
  150. }
  151. }
  152. }