RemotePlugin.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. * @author John Molakvoæ <skjnldsv@protonmail.com>
  9. * @author Julius Härtl <jus@bitgrid.net>
  10. *
  11. * @license GNU AGPL version 3 or any later version
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License as
  15. * published by the Free Software Foundation, either version 3 of the
  16. * License, or (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *
  26. */
  27. namespace OC\Collaboration\Collaborators;
  28. use OCP\Collaboration\Collaborators\ISearchPlugin;
  29. use OCP\Collaboration\Collaborators\ISearchResult;
  30. use OCP\Collaboration\Collaborators\SearchResultType;
  31. use OCP\Contacts\IManager;
  32. use OCP\Federation\ICloudIdManager;
  33. use OCP\IConfig;
  34. use OCP\IUserManager;
  35. use OCP\IUserSession;
  36. use OCP\Share\IShare;
  37. class RemotePlugin implements ISearchPlugin {
  38. protected bool $shareeEnumeration;
  39. private string $userId;
  40. public function __construct(
  41. private IManager $contactsManager,
  42. private ICloudIdManager $cloudIdManager,
  43. private IConfig $config,
  44. private IUserManager $userManager,
  45. IUserSession $userSession,
  46. ) {
  47. $this->userId = $userSession->getUser()?->getUID() ?? '';
  48. $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
  49. }
  50. public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
  51. $result = ['wide' => [], 'exact' => []];
  52. $resultType = new SearchResultType('remotes');
  53. // Search in contacts
  54. $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN'], [
  55. 'limit' => $limit,
  56. 'offset' => $offset,
  57. 'enumeration' => false,
  58. 'fullmatch' => false,
  59. ]);
  60. foreach ($addressBookContacts as $contact) {
  61. if (isset($contact['isLocalSystemBook'])) {
  62. continue;
  63. }
  64. if (isset($contact['CLOUD'])) {
  65. $cloudIds = $contact['CLOUD'];
  66. if (is_string($cloudIds)) {
  67. $cloudIds = [$cloudIds];
  68. }
  69. $lowerSearch = strtolower($search);
  70. foreach ($cloudIds as $cloudId) {
  71. $cloudIdType = '';
  72. if (\is_array($cloudId)) {
  73. $cloudIdData = $cloudId;
  74. $cloudId = $cloudIdData['value'];
  75. $cloudIdType = $cloudIdData['type'];
  76. }
  77. try {
  78. [$remoteUser, $serverUrl] = $this->splitUserRemote($cloudId);
  79. } catch (\InvalidArgumentException $e) {
  80. continue;
  81. }
  82. $localUser = $this->userManager->get($remoteUser);
  83. /**
  84. * Add local share if remote cloud id matches a local user ones
  85. */
  86. if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId()) {
  87. $result['wide'][] = [
  88. 'label' => $contact['FN'],
  89. 'uuid' => $contact['UID'],
  90. 'value' => [
  91. 'shareType' => IShare::TYPE_USER,
  92. 'shareWith' => $remoteUser
  93. ],
  94. 'shareWithDisplayNameUnique' => $contact['EMAIL'] !== null && $contact['EMAIL'] !== '' ? $contact['EMAIL'] : $contact['UID'],
  95. ];
  96. }
  97. if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) {
  98. if (strtolower($cloudId) === $lowerSearch) {
  99. $searchResult->markExactIdMatch($resultType);
  100. }
  101. $result['exact'][] = [
  102. 'label' => $contact['FN'] . " ($cloudId)",
  103. 'uuid' => $contact['UID'],
  104. 'name' => $contact['FN'],
  105. 'type' => $cloudIdType,
  106. 'value' => [
  107. 'shareType' => IShare::TYPE_REMOTE,
  108. 'shareWith' => $cloudId,
  109. 'server' => $serverUrl,
  110. ],
  111. ];
  112. } else {
  113. $result['wide'][] = [
  114. 'label' => $contact['FN'] . " ($cloudId)",
  115. 'uuid' => $contact['UID'],
  116. 'name' => $contact['FN'],
  117. 'type' => $cloudIdType,
  118. 'value' => [
  119. 'shareType' => IShare::TYPE_REMOTE,
  120. 'shareWith' => $cloudId,
  121. 'server' => $serverUrl,
  122. ],
  123. ];
  124. }
  125. }
  126. }
  127. }
  128. if (!$this->shareeEnumeration) {
  129. $result['wide'] = [];
  130. } else {
  131. $result['wide'] = array_slice($result['wide'], $offset, $limit);
  132. }
  133. /**
  134. * Add generic share with remote item for valid cloud ids that are not users of the local instance
  135. */
  136. if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) {
  137. try {
  138. [$remoteUser, $serverUrl] = $this->splitUserRemote($search);
  139. $localUser = $this->userManager->get($remoteUser);
  140. if ($localUser === null || $search !== $localUser->getCloudId()) {
  141. $result['exact'][] = [
  142. 'label' => $remoteUser . " ($serverUrl)",
  143. 'uuid' => $remoteUser,
  144. 'name' => $remoteUser,
  145. 'value' => [
  146. 'shareType' => IShare::TYPE_REMOTE,
  147. 'shareWith' => $search,
  148. 'server' => $serverUrl,
  149. ],
  150. ];
  151. }
  152. } catch (\InvalidArgumentException $e) {
  153. }
  154. }
  155. $searchResult->addResultSet($resultType, $result['wide'], $result['exact']);
  156. return true;
  157. }
  158. /**
  159. * split user and remote from federated cloud id
  160. *
  161. * @param string $address federated share address
  162. * @return array [user, remoteURL]
  163. * @throws \InvalidArgumentException
  164. */
  165. public function splitUserRemote(string $address): array {
  166. try {
  167. $cloudId = $this->cloudIdManager->resolveCloudId($address);
  168. return [$cloudId->getUser(), $cloudId->getRemote()];
  169. } catch (\InvalidArgumentException $e) {
  170. throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e);
  171. }
  172. }
  173. }