You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2062 lines
64 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bjoern Schiessle <bjoern@schiessle.org>
  7. * @author Björn Schießle <bjoern@schiessle.org>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
  10. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  11. * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
  12. * @author Joas Schilling <coding@schilljs.com>
  13. * @author John Molakvoæ <skjnldsv@protonmail.com>
  14. * @author Julius Härtl <jus@bitgrid.net>
  15. * @author Lukas Reschke <lukas@statuscode.ch>
  16. * @author Maxence Lange <maxence@artificial-owl.com>
  17. * @author Maxence Lange <maxence@nextcloud.com>
  18. * @author Morris Jobke <hey@morrisjobke.de>
  19. * @author Pauli Järvinen <pauli.jarvinen@gmail.com>
  20. * @author Robin Appelman <robin@icewind.nl>
  21. * @author Roeland Jago Douma <roeland@famdouma.nl>
  22. * @author Samuel <faust64@gmail.com>
  23. * @author szaimen <szaimen@e.mail.de>
  24. * @author Valdnet <47037905+Valdnet@users.noreply.github.com>
  25. * @author Vincent Petry <vincent@nextcloud.com>
  26. *
  27. * @license AGPL-3.0
  28. *
  29. * This code is free software: you can redistribute it and/or modify
  30. * it under the terms of the GNU Affero General Public License, version 3,
  31. * as published by the Free Software Foundation.
  32. *
  33. * This program is distributed in the hope that it will be useful,
  34. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  35. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  36. * GNU Affero General Public License for more details.
  37. *
  38. * You should have received a copy of the GNU Affero General Public License, version 3,
  39. * along with this program. If not, see <http://www.gnu.org/licenses/>
  40. *
  41. */
  42. namespace OC\Share20;
  43. use OC\Files\Mount\MoveableMount;
  44. use OC\KnownUser\KnownUserService;
  45. use OC\Share20\Exception\ProviderException;
  46. use OCA\Files_Sharing\AppInfo\Application;
  47. use OCA\Files_Sharing\ISharedStorage;
  48. use OCP\EventDispatcher\IEventDispatcher;
  49. use OCP\Files\File;
  50. use OCP\Files\Folder;
  51. use OCP\Files\IRootFolder;
  52. use OCP\Files\Mount\IMountManager;
  53. use OCP\Files\Node;
  54. use OCP\HintException;
  55. use OCP\IConfig;
  56. use OCP\IGroupManager;
  57. use OCP\IL10N;
  58. use OCP\IURLGenerator;
  59. use OCP\IUser;
  60. use OCP\IUserManager;
  61. use OCP\IUserSession;
  62. use OCP\L10N\IFactory;
  63. use OCP\Mail\IMailer;
  64. use OCP\Security\Events\ValidatePasswordPolicyEvent;
  65. use OCP\Security\IHasher;
  66. use OCP\Security\ISecureRandom;
  67. use OCP\Share;
  68. use OCP\Share\Events\BeforeShareDeletedEvent;
  69. use OCP\Share\Events\ShareAcceptedEvent;
  70. use OCP\Share\Events\ShareCreatedEvent;
  71. use OCP\Share\Events\ShareDeletedEvent;
  72. use OCP\Share\Events\ShareDeletedFromSelfEvent;
  73. use OCP\Share\Exceptions\AlreadySharedException;
  74. use OCP\Share\Exceptions\GenericShareException;
  75. use OCP\Share\Exceptions\ShareNotFound;
  76. use OCP\Share\IManager;
  77. use OCP\Share\IProviderFactory;
  78. use OCP\Share\IShare;
  79. use OCP\Share\IShareProvider;
  80. use Psr\Log\LoggerInterface;
  81. /**
  82. * This class is the communication hub for all sharing related operations.
  83. */
  84. class Manager implements IManager {
  85. /** @var IProviderFactory */
  86. private $factory;
  87. private LoggerInterface $logger;
  88. /** @var IConfig */
  89. private $config;
  90. /** @var ISecureRandom */
  91. private $secureRandom;
  92. /** @var IHasher */
  93. private $hasher;
  94. /** @var IMountManager */
  95. private $mountManager;
  96. /** @var IGroupManager */
  97. private $groupManager;
  98. /** @var IL10N */
  99. private $l;
  100. /** @var IFactory */
  101. private $l10nFactory;
  102. /** @var IUserManager */
  103. private $userManager;
  104. /** @var IRootFolder */
  105. private $rootFolder;
  106. /** @var LegacyHooks */
  107. private $legacyHooks;
  108. /** @var IMailer */
  109. private $mailer;
  110. /** @var IURLGenerator */
  111. private $urlGenerator;
  112. /** @var \OC_Defaults */
  113. private $defaults;
  114. /** @var IEventDispatcher */
  115. private $dispatcher;
  116. /** @var IUserSession */
  117. private $userSession;
  118. /** @var KnownUserService */
  119. private $knownUserService;
  120. private ShareDisableChecker $shareDisableChecker;
  121. public function __construct(
  122. LoggerInterface $logger,
  123. IConfig $config,
  124. ISecureRandom $secureRandom,
  125. IHasher $hasher,
  126. IMountManager $mountManager,
  127. IGroupManager $groupManager,
  128. IL10N $l,
  129. IFactory $l10nFactory,
  130. IProviderFactory $factory,
  131. IUserManager $userManager,
  132. IRootFolder $rootFolder,
  133. IMailer $mailer,
  134. IURLGenerator $urlGenerator,
  135. \OC_Defaults $defaults,
  136. IEventDispatcher $dispatcher,
  137. IUserSession $userSession,
  138. KnownUserService $knownUserService,
  139. ShareDisableChecker $shareDisableChecker
  140. ) {
  141. $this->logger = $logger;
  142. $this->config = $config;
  143. $this->secureRandom = $secureRandom;
  144. $this->hasher = $hasher;
  145. $this->mountManager = $mountManager;
  146. $this->groupManager = $groupManager;
  147. $this->l = $l;
  148. $this->l10nFactory = $l10nFactory;
  149. $this->factory = $factory;
  150. $this->userManager = $userManager;
  151. $this->rootFolder = $rootFolder;
  152. // The constructor of LegacyHooks registers the listeners of share events
  153. // do not remove if those are not properly migrated
  154. $this->legacyHooks = new LegacyHooks($dispatcher);
  155. $this->mailer = $mailer;
  156. $this->urlGenerator = $urlGenerator;
  157. $this->defaults = $defaults;
  158. $this->dispatcher = $dispatcher;
  159. $this->userSession = $userSession;
  160. $this->knownUserService = $knownUserService;
  161. $this->shareDisableChecker = $shareDisableChecker;
  162. }
  163. /**
  164. * Convert from a full share id to a tuple (providerId, shareId)
  165. *
  166. * @param string $id
  167. * @return string[]
  168. */
  169. private function splitFullId($id) {
  170. return explode(':', $id, 2);
  171. }
  172. /**
  173. * Verify if a password meets all requirements
  174. *
  175. * @param string $password
  176. * @throws \Exception
  177. */
  178. protected function verifyPassword($password) {
  179. if ($password === null) {
  180. // No password is set, check if this is allowed.
  181. if ($this->shareApiLinkEnforcePassword()) {
  182. throw new \InvalidArgumentException('Passwords are enforced for link and mail shares');
  183. }
  184. return;
  185. }
  186. // Let others verify the password
  187. try {
  188. $this->dispatcher->dispatchTyped(new ValidatePasswordPolicyEvent($password));
  189. } catch (HintException $e) {
  190. throw new \Exception($e->getHint());
  191. }
  192. }
  193. /**
  194. * Check for generic requirements before creating a share
  195. *
  196. * @param IShare $share
  197. * @throws \InvalidArgumentException
  198. * @throws GenericShareException
  199. *
  200. * @suppress PhanUndeclaredClassMethod
  201. */
  202. protected function generalCreateChecks(IShare $share) {
  203. if ($share->getShareType() === IShare::TYPE_USER) {
  204. // We expect a valid user as sharedWith for user shares
  205. if (!$this->userManager->userExists($share->getSharedWith())) {
  206. throw new \InvalidArgumentException('SharedWith is not a valid user');
  207. }
  208. } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
  209. // We expect a valid group as sharedWith for group shares
  210. if (!$this->groupManager->groupExists($share->getSharedWith())) {
  211. throw new \InvalidArgumentException('SharedWith is not a valid group');
  212. }
  213. } elseif ($share->getShareType() === IShare::TYPE_LINK) {
  214. // No check for TYPE_EMAIL here as we have a recipient for them
  215. if ($share->getSharedWith() !== null) {
  216. throw new \InvalidArgumentException('SharedWith should be empty');
  217. }
  218. } elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
  219. if ($share->getSharedWith() === null) {
  220. throw new \InvalidArgumentException('SharedWith should not be empty');
  221. }
  222. } elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
  223. if ($share->getSharedWith() === null) {
  224. throw new \InvalidArgumentException('SharedWith should not be empty');
  225. }
  226. } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
  227. if ($share->getSharedWith() === null) {
  228. throw new \InvalidArgumentException('SharedWith should not be empty');
  229. }
  230. } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
  231. $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
  232. if ($circle === null) {
  233. throw new \InvalidArgumentException('SharedWith is not a valid circle');
  234. }
  235. } elseif ($share->getShareType() === IShare::TYPE_ROOM) {
  236. } elseif ($share->getShareType() === IShare::TYPE_DECK) {
  237. } elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
  238. } else {
  239. // We cannot handle other types yet
  240. throw new \InvalidArgumentException('unknown share type');
  241. }
  242. // Verify the initiator of the share is set
  243. if ($share->getSharedBy() === null) {
  244. throw new \InvalidArgumentException('SharedBy should be set');
  245. }
  246. // Cannot share with yourself
  247. if ($share->getShareType() === IShare::TYPE_USER &&
  248. $share->getSharedWith() === $share->getSharedBy()) {
  249. throw new \InvalidArgumentException('Cannot share with yourself');
  250. }
  251. // The path should be set
  252. if ($share->getNode() === null) {
  253. throw new \InvalidArgumentException('Path should be set');
  254. }
  255. // And it should be a file or a folder
  256. if (!($share->getNode() instanceof \OCP\Files\File) &&
  257. !($share->getNode() instanceof \OCP\Files\Folder)) {
  258. throw new \InvalidArgumentException('Path should be either a file or a folder');
  259. }
  260. // And you cannot share your rootfolder
  261. if ($this->userManager->userExists($share->getSharedBy())) {
  262. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  263. } else {
  264. $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  265. }
  266. if ($userFolder->getId() === $share->getNode()->getId()) {
  267. throw new \InvalidArgumentException('You cannot share your root folder');
  268. }
  269. // Check if we actually have share permissions
  270. if (!$share->getNode()->isShareable()) {
  271. $message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getName()]);
  272. throw new GenericShareException($message_t, $message_t, 404);
  273. }
  274. // Permissions should be set
  275. if ($share->getPermissions() === null) {
  276. throw new \InvalidArgumentException('A share requires permissions');
  277. }
  278. $isFederatedShare = $share->getNode()->getStorage()->instanceOfStorage('\OCA\Files_Sharing\External\Storage');
  279. $permissions = 0;
  280. if (!$isFederatedShare && $share->getNode()->getOwner() && $share->getNode()->getOwner()->getUID() !== $share->getSharedBy()) {
  281. $userMounts = array_filter($userFolder->getById($share->getNode()->getId()), function ($mount) {
  282. // We need to filter since there might be other mountpoints that contain the file
  283. // e.g. if the user has access to the same external storage that the file is originating from
  284. return $mount->getStorage()->instanceOfStorage(ISharedStorage::class);
  285. });
  286. $userMount = array_shift($userMounts);
  287. if ($userMount === null) {
  288. throw new GenericShareException('Could not get proper share mount for ' . $share->getNode()->getId() . '. Failing since else the next calls are called with null');
  289. }
  290. $mount = $userMount->getMountPoint();
  291. // When it's a reshare use the parent share permissions as maximum
  292. $userMountPointId = $mount->getStorageRootId();
  293. $userMountPoints = $userFolder->getById($userMountPointId);
  294. $userMountPoint = array_shift($userMountPoints);
  295. if ($userMountPoint === null) {
  296. throw new GenericShareException('Could not get proper user mount for ' . $userMountPointId . '. Failing since else the next calls are called with null');
  297. }
  298. /* Check if this is an incoming share */
  299. $incomingShares = $this->getSharedWith($share->getSharedBy(), IShare::TYPE_USER, $userMountPoint, -1, 0);
  300. $incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_GROUP, $userMountPoint, -1, 0));
  301. $incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_CIRCLE, $userMountPoint, -1, 0));
  302. $incomingShares = array_merge($incomingShares, $this->getSharedWith($share->getSharedBy(), IShare::TYPE_ROOM, $userMountPoint, -1, 0));
  303. /** @var IShare[] $incomingShares */
  304. if (!empty($incomingShares)) {
  305. foreach ($incomingShares as $incomingShare) {
  306. $permissions |= $incomingShare->getPermissions();
  307. }
  308. }
  309. } else {
  310. /*
  311. * Quick fix for #23536
  312. * Non moveable mount points do not have update and delete permissions
  313. * while we 'most likely' do have that on the storage.
  314. */
  315. $permissions = $share->getNode()->getPermissions();
  316. if (!($share->getNode()->getMountPoint() instanceof MoveableMount)) {
  317. $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
  318. }
  319. }
  320. // Check that we do not share with more permissions than we have
  321. if ($share->getPermissions() & ~$permissions) {
  322. $path = $userFolder->getRelativePath($share->getNode()->getPath());
  323. $message_t = $this->l->t('Cannot increase permissions of %s', [$path]);
  324. throw new GenericShareException($message_t, $message_t, 404);
  325. }
  326. // Check that read permissions are always set
  327. // Link shares are allowed to have no read permissions to allow upload to hidden folders
  328. $noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK
  329. || $share->getShareType() === IShare::TYPE_EMAIL;
  330. if (!$noReadPermissionRequired &&
  331. ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
  332. throw new \InvalidArgumentException('Shares need at least read permissions');
  333. }
  334. if ($share->getNode() instanceof \OCP\Files\File) {
  335. if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
  336. $message_t = $this->l->t('Files cannot be shared with delete permissions');
  337. throw new GenericShareException($message_t);
  338. }
  339. if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
  340. $message_t = $this->l->t('Files cannot be shared with create permissions');
  341. throw new GenericShareException($message_t);
  342. }
  343. }
  344. }
  345. /**
  346. * Validate if the expiration date fits the system settings
  347. *
  348. * @param IShare $share The share to validate the expiration date of
  349. * @return IShare The modified share object
  350. * @throws GenericShareException
  351. * @throws \InvalidArgumentException
  352. * @throws \Exception
  353. */
  354. protected function validateExpirationDateInternal(IShare $share) {
  355. $isRemote = $share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP;
  356. $expirationDate = $share->getExpirationDate();
  357. if ($expirationDate !== null) {
  358. //Make sure the expiration date is a date
  359. $expirationDate->setTime(0, 0, 0);
  360. $date = new \DateTime();
  361. $date->setTime(0, 0, 0);
  362. if ($date >= $expirationDate) {
  363. $message = $this->l->t('Expiration date is in the past');
  364. throw new GenericShareException($message, $message, 404);
  365. }
  366. }
  367. // If expiredate is empty set a default one if there is a default
  368. $fullId = null;
  369. try {
  370. $fullId = $share->getFullId();
  371. } catch (\UnexpectedValueException $e) {
  372. // This is a new share
  373. }
  374. if ($isRemote) {
  375. $defaultExpireDate = $this->shareApiRemoteDefaultExpireDate();
  376. $defaultExpireDays = $this->shareApiRemoteDefaultExpireDays();
  377. $configProp = 'remote_defaultExpDays';
  378. $isEnforced = $this->shareApiRemoteDefaultExpireDateEnforced();
  379. } else {
  380. $defaultExpireDate = $this->shareApiInternalDefaultExpireDate();
  381. $defaultExpireDays = $this->shareApiInternalDefaultExpireDays();
  382. $configProp = 'internal_defaultExpDays';
  383. $isEnforced = $this->shareApiInternalDefaultExpireDateEnforced();
  384. }
  385. if ($fullId === null && $expirationDate === null && $defaultExpireDate) {
  386. $expirationDate = new \DateTime();
  387. $expirationDate->setTime(0, 0, 0);
  388. $days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays);
  389. if ($days > $defaultExpireDays) {
  390. $days = $defaultExpireDays;
  391. }
  392. $expirationDate->add(new \DateInterval('P' . $days . 'D'));
  393. }
  394. // If we enforce the expiration date check that is does not exceed
  395. if ($isEnforced) {
  396. if ($expirationDate === null) {
  397. throw new \InvalidArgumentException('Expiration date is enforced');
  398. }
  399. $date = new \DateTime();
  400. $date->setTime(0, 0, 0);
  401. $date->add(new \DateInterval('P' . $defaultExpireDays . 'D'));
  402. if ($date < $expirationDate) {
  403. $message = $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays);
  404. throw new GenericShareException($message, $message, 404);
  405. }
  406. }
  407. $accepted = true;
  408. $message = '';
  409. \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
  410. 'expirationDate' => &$expirationDate,
  411. 'accepted' => &$accepted,
  412. 'message' => &$message,
  413. 'passwordSet' => $share->getPassword() !== null,
  414. ]);
  415. if (!$accepted) {
  416. throw new \Exception($message);
  417. }
  418. $share->setExpirationDate($expirationDate);
  419. return $share;
  420. }
  421. /**
  422. * Validate if the expiration date fits the system settings
  423. *
  424. * @param IShare $share The share to validate the expiration date of
  425. * @return IShare The modified share object
  426. * @throws GenericShareException
  427. * @throws \InvalidArgumentException
  428. * @throws \Exception
  429. */
  430. protected function validateExpirationDateLink(IShare $share) {
  431. $expirationDate = $share->getExpirationDate();
  432. if ($expirationDate !== null) {
  433. //Make sure the expiration date is a date
  434. $expirationDate->setTime(0, 0, 0);
  435. $date = new \DateTime();
  436. $date->setTime(0, 0, 0);
  437. if ($date >= $expirationDate) {
  438. $message = $this->l->t('Expiration date is in the past');
  439. throw new GenericShareException($message, $message, 404);
  440. }
  441. }
  442. // If expiredate is empty set a default one if there is a default
  443. $fullId = null;
  444. try {
  445. $fullId = $share->getFullId();
  446. } catch (\UnexpectedValueException $e) {
  447. // This is a new share
  448. }
  449. if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
  450. $expirationDate = new \DateTime();
  451. $expirationDate->setTime(0, 0, 0);
  452. $days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays());
  453. if ($days > $this->shareApiLinkDefaultExpireDays()) {
  454. $days = $this->shareApiLinkDefaultExpireDays();
  455. }
  456. $expirationDate->add(new \DateInterval('P' . $days . 'D'));
  457. }
  458. // If we enforce the expiration date check that is does not exceed
  459. if ($this->shareApiLinkDefaultExpireDateEnforced()) {
  460. if ($expirationDate === null) {
  461. throw new \InvalidArgumentException('Expiration date is enforced');
  462. }
  463. $date = new \DateTime();
  464. $date->setTime(0, 0, 0);
  465. $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
  466. if ($date < $expirationDate) {
  467. $message = $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays());
  468. throw new GenericShareException($message, $message, 404);
  469. }
  470. }
  471. $accepted = true;
  472. $message = '';
  473. \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
  474. 'expirationDate' => &$expirationDate,
  475. 'accepted' => &$accepted,
  476. 'message' => &$message,
  477. 'passwordSet' => $share->getPassword() !== null,
  478. ]);
  479. if (!$accepted) {
  480. throw new \Exception($message);
  481. }
  482. $share->setExpirationDate($expirationDate);
  483. return $share;
  484. }
  485. /**
  486. * Check for pre share requirements for user shares
  487. *
  488. * @param IShare $share
  489. * @throws \Exception
  490. */
  491. protected function userCreateChecks(IShare $share) {
  492. // Check if we can share with group members only
  493. if ($this->shareWithGroupMembersOnly()) {
  494. $sharedBy = $this->userManager->get($share->getSharedBy());
  495. $sharedWith = $this->userManager->get($share->getSharedWith());
  496. // Verify we can share with this user
  497. $groups = array_intersect(
  498. $this->groupManager->getUserGroupIds($sharedBy),
  499. $this->groupManager->getUserGroupIds($sharedWith)
  500. );
  501. if (empty($groups)) {
  502. $message_t = $this->l->t('Sharing is only allowed with group members');
  503. throw new \Exception($message_t);
  504. }
  505. }
  506. /*
  507. * TODO: Could be costly, fix
  508. *
  509. * Also this is not what we want in the future.. then we want to squash identical shares.
  510. */
  511. $provider = $this->factory->getProviderForType(IShare::TYPE_USER);
  512. $existingShares = $provider->getSharesByPath($share->getNode());
  513. foreach ($existingShares as $existingShare) {
  514. // Ignore if it is the same share
  515. try {
  516. if ($existingShare->getFullId() === $share->getFullId()) {
  517. continue;
  518. }
  519. } catch (\UnexpectedValueException $e) {
  520. //Shares are not identical
  521. }
  522. // Identical share already exists
  523. if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
  524. $message = $this->l->t('Sharing %s failed, because this item is already shared with user %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]);
  525. throw new AlreadySharedException($message, $existingShare);
  526. }
  527. // The share is already shared with this user via a group share
  528. if ($existingShare->getShareType() === IShare::TYPE_GROUP) {
  529. $group = $this->groupManager->get($existingShare->getSharedWith());
  530. if (!is_null($group)) {
  531. $user = $this->userManager->get($share->getSharedWith());
  532. if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
  533. $message = $this->l->t('Sharing %s failed, because this item is already shared with user %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]);
  534. throw new AlreadySharedException($message, $existingShare);
  535. }
  536. }
  537. }
  538. }
  539. }
  540. /**
  541. * Check for pre share requirements for group shares
  542. *
  543. * @param IShare $share
  544. * @throws \Exception
  545. */
  546. protected function groupCreateChecks(IShare $share) {
  547. // Verify group shares are allowed
  548. if (!$this->allowGroupSharing()) {
  549. throw new \Exception('Group sharing is now allowed');
  550. }
  551. // Verify if the user can share with this group
  552. if ($this->shareWithGroupMembersOnly()) {
  553. $sharedBy = $this->userManager->get($share->getSharedBy());
  554. $sharedWith = $this->groupManager->get($share->getSharedWith());
  555. if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
  556. throw new \Exception('Sharing is only allowed within your own groups');
  557. }
  558. }
  559. /*
  560. * TODO: Could be costly, fix
  561. *
  562. * Also this is not what we want in the future.. then we want to squash identical shares.
  563. */
  564. $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
  565. $existingShares = $provider->getSharesByPath($share->getNode());
  566. foreach ($existingShares as $existingShare) {
  567. try {
  568. if ($existingShare->getFullId() === $share->getFullId()) {
  569. continue;
  570. }
  571. } catch (\UnexpectedValueException $e) {
  572. //It is a new share so just continue
  573. }
  574. if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
  575. throw new AlreadySharedException('Path is already shared with this group', $existingShare);
  576. }
  577. }
  578. }
  579. /**
  580. * Check for pre share requirements for link shares
  581. *
  582. * @param IShare $share
  583. * @throws \Exception
  584. */
  585. protected function linkCreateChecks(IShare $share) {
  586. // Are link shares allowed?
  587. if (!$this->shareApiAllowLinks()) {
  588. throw new \Exception('Link sharing is not allowed');
  589. }
  590. // Check if public upload is allowed
  591. if ($share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload() &&
  592. ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
  593. throw new \InvalidArgumentException('Public upload is not allowed');
  594. }
  595. }
  596. /**
  597. * To make sure we don't get invisible link shares we set the parent
  598. * of a link if it is a reshare. This is a quick word around
  599. * until we can properly display multiple link shares in the UI
  600. *
  601. * See: https://github.com/owncloud/core/issues/22295
  602. *
  603. * FIXME: Remove once multiple link shares can be properly displayed
  604. *
  605. * @param IShare $share
  606. */
  607. protected function setLinkParent(IShare $share) {
  608. // No sense in checking if the method is not there.
  609. if (method_exists($share, 'setParent')) {
  610. $storage = $share->getNode()->getStorage();
  611. if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
  612. /** @var \OCA\Files_Sharing\SharedStorage $storage */
  613. $share->setParent($storage->getShareId());
  614. }
  615. }
  616. }
  617. /**
  618. * @param File|Folder $path
  619. */
  620. protected function pathCreateChecks($path) {
  621. // Make sure that we do not share a path that contains a shared mountpoint
  622. if ($path instanceof \OCP\Files\Folder) {
  623. $mounts = $this->mountManager->findIn($path->getPath());
  624. foreach ($mounts as $mount) {
  625. if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
  626. throw new \InvalidArgumentException('Path contains files shared with you');
  627. }
  628. }
  629. }
  630. }
  631. /**
  632. * Check if the user that is sharing can actually share
  633. *
  634. * @param IShare $share
  635. * @throws \Exception
  636. */
  637. protected function canShare(IShare $share) {
  638. if (!$this->shareApiEnabled()) {
  639. throw new \Exception('Sharing is disabled');
  640. }
  641. if ($this->sharingDisabledForUser($share->getSharedBy())) {
  642. throw new \Exception('Sharing is disabled for you');
  643. }
  644. }
  645. /**
  646. * Share a path
  647. *
  648. * @param IShare $share
  649. * @return IShare The share object
  650. * @throws \Exception
  651. *
  652. * TODO: handle link share permissions or check them
  653. */
  654. public function createShare(IShare $share) {
  655. $this->canShare($share);
  656. $this->generalCreateChecks($share);
  657. // Verify if there are any issues with the path
  658. $this->pathCreateChecks($share->getNode());
  659. /*
  660. * On creation of a share the owner is always the owner of the path
  661. * Except for mounted federated shares.
  662. */
  663. $storage = $share->getNode()->getStorage();
  664. if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
  665. $parent = $share->getNode()->getParent();
  666. while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
  667. $parent = $parent->getParent();
  668. }
  669. $share->setShareOwner($parent->getOwner()->getUID());
  670. } else {
  671. if ($share->getNode()->getOwner()) {
  672. $share->setShareOwner($share->getNode()->getOwner()->getUID());
  673. } else {
  674. $share->setShareOwner($share->getSharedBy());
  675. }
  676. }
  677. try {
  678. // Verify share type
  679. if ($share->getShareType() === IShare::TYPE_USER) {
  680. $this->userCreateChecks($share);
  681. // Verify the expiration date
  682. $share = $this->validateExpirationDateInternal($share);
  683. } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
  684. $this->groupCreateChecks($share);
  685. // Verify the expiration date
  686. $share = $this->validateExpirationDateInternal($share);
  687. } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
  688. //Verify the expiration date
  689. $share = $this->validateExpirationDateInternal($share);
  690. } elseif ($share->getShareType() === IShare::TYPE_LINK
  691. || $share->getShareType() === IShare::TYPE_EMAIL) {
  692. $this->linkCreateChecks($share);
  693. $this->setLinkParent($share);
  694. // For now ignore a set token.
  695. $share->setToken(
  696. $this->secureRandom->generate(
  697. \OC\Share\Constants::TOKEN_LENGTH,
  698. \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
  699. )
  700. );
  701. // Verify the expiration date
  702. $share = $this->validateExpirationDateLink($share);
  703. // Verify the password
  704. $this->verifyPassword($share->getPassword());
  705. // If a password is set. Hash it!
  706. if ($share->getShareType() === IShare::TYPE_LINK
  707. && $share->getPassword() !== null) {
  708. $share->setPassword($this->hasher->hash($share->getPassword()));
  709. }
  710. }
  711. // Cannot share with the owner
  712. if ($share->getShareType() === IShare::TYPE_USER &&
  713. $share->getSharedWith() === $share->getShareOwner()) {
  714. throw new \InvalidArgumentException('Cannot share with the share owner');
  715. }
  716. // Generate the target
  717. $defaultShareFolder = $this->config->getSystemValue('share_folder', '/');
  718. $allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
  719. if ($allowCustomShareFolder) {
  720. $shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $defaultShareFolder);
  721. } else {
  722. $shareFolder = $defaultShareFolder;
  723. }
  724. $target = $shareFolder . '/' . $share->getNode()->getName();
  725. $target = \OC\Files\Filesystem::normalizePath($target);
  726. $share->setTarget($target);
  727. // Pre share event
  728. $event = new Share\Events\BeforeShareCreatedEvent($share);
  729. $this->dispatcher->dispatchTyped($event);
  730. if ($event->isPropagationStopped() && $event->getError()) {
  731. throw new \Exception($event->getError());
  732. }
  733. $oldShare = $share;
  734. $provider = $this->factory->getProviderForType($share->getShareType());
  735. $share = $provider->create($share);
  736. // Reuse the node we already have
  737. $share->setNode($oldShare->getNode());
  738. // Reset the target if it is null for the new share
  739. if ($share->getTarget() === '') {
  740. $share->setTarget($target);
  741. }
  742. } catch (AlreadySharedException $e) {
  743. // if a share for the same target already exists, dont create a new one, but do trigger the hooks and notifications again
  744. $oldShare = $share;
  745. // Reuse the node we already have
  746. $share = $e->getExistingShare();
  747. $share->setNode($oldShare->getNode());
  748. }
  749. // Post share event
  750. $this->dispatcher->dispatchTyped(new ShareCreatedEvent($share));
  751. if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)
  752. && $share->getShareType() === IShare::TYPE_USER) {
  753. $mailSend = $share->getMailSend();
  754. if ($mailSend === true) {
  755. $user = $this->userManager->get($share->getSharedWith());
  756. if ($user !== null) {
  757. $emailAddress = $user->getEMailAddress();
  758. if ($emailAddress !== null && $emailAddress !== '') {
  759. $userLang = $this->l10nFactory->getUserLanguage($user);
  760. $l = $this->l10nFactory->get('lib', $userLang);
  761. $this->sendMailNotification(
  762. $l,
  763. $share->getNode()->getName(),
  764. $this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.accept', ['shareId' => $share->getFullId()]),
  765. $share->getSharedBy(),
  766. $emailAddress,
  767. $share->getExpirationDate(),
  768. $share->getNote()
  769. );
  770. $this->logger->debug('Sent share notification to ' . $emailAddress . ' for share with ID ' . $share->getId(), ['app' => 'share']);
  771. } else {
  772. $this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']);
  773. }
  774. } else {
  775. $this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']);
  776. }
  777. } else {
  778. $this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
  779. }
  780. }
  781. return $share;
  782. }
  783. /**
  784. * Send mail notifications
  785. *
  786. * This method will catch and log mail transmission errors
  787. *
  788. * @param IL10N $l Language of the recipient
  789. * @param string $filename file/folder name
  790. * @param string $link link to the file/folder
  791. * @param string $initiator user ID of share sender
  792. * @param string $shareWith email address of share receiver
  793. * @param \DateTime|null $expiration
  794. */
  795. protected function sendMailNotification(IL10N $l,
  796. $filename,
  797. $link,
  798. $initiator,
  799. $shareWith,
  800. \DateTime $expiration = null,
  801. $note = '') {
  802. $initiatorUser = $this->userManager->get($initiator);
  803. $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
  804. $message = $this->mailer->createMessage();
  805. $emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [
  806. 'filename' => $filename,
  807. 'link' => $link,
  808. 'initiator' => $initiatorDisplayName,
  809. 'expiration' => $expiration,
  810. 'shareWith' => $shareWith,
  811. ]);
  812. $emailTemplate->setSubject($l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]));
  813. $emailTemplate->addHeader();
  814. $emailTemplate->addHeading($l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]), false);
  815. $text = $l->t('%1$s shared »%2$s« with you.', [$initiatorDisplayName, $filename]);
  816. if ($note !== '') {
  817. $emailTemplate->addBodyText(htmlspecialchars($note), $note);
  818. }
  819. $emailTemplate->addBodyText(
  820. htmlspecialchars($text . ' ' . $l->t('Click the button below to open it.')),
  821. $text
  822. );
  823. $emailTemplate->addBodyButton(
  824. $l->t('Open »%s«', [$filename]),
  825. $link
  826. );
  827. $message->setTo([$shareWith]);
  828. // The "From" contains the sharers name
  829. $instanceName = $this->defaults->getName();
  830. $senderName = $l->t(
  831. '%1$s via %2$s',
  832. [
  833. $initiatorDisplayName,
  834. $instanceName,
  835. ]
  836. );
  837. $message->setFrom([\OCP\Util::getDefaultEmailAddress('noreply') => $senderName]);
  838. // The "Reply-To" is set to the sharer if an mail address is configured
  839. // also the default footer contains a "Do not reply" which needs to be adjusted.
  840. $initiatorEmail = $initiatorUser->getEMailAddress();
  841. if ($initiatorEmail !== null) {
  842. $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
  843. $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan($l->getLanguageCode()) !== '' ? ' - ' . $this->defaults->getSlogan($l->getLanguageCode()) : ''));
  844. } else {
  845. $emailTemplate->addFooter('', $l->getLanguageCode());
  846. }
  847. $message->useTemplate($emailTemplate);
  848. try {
  849. $failedRecipients = $this->mailer->send($message);
  850. if (!empty($failedRecipients)) {
  851. $this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
  852. return;
  853. }
  854. } catch (\Exception $e) {
  855. $this->logger->error('Share notification mail could not be sent', ['exception' => $e]);
  856. }
  857. }
  858. /**
  859. * Update a share
  860. *
  861. * @param IShare $share
  862. * @return IShare The share object
  863. * @throws \InvalidArgumentException
  864. */
  865. public function updateShare(IShare $share) {
  866. $expirationDateUpdated = false;
  867. $this->canShare($share);
  868. try {
  869. $originalShare = $this->getShareById($share->getFullId());
  870. } catch (\UnexpectedValueException $e) {
  871. throw new \InvalidArgumentException('Share does not have a full id');
  872. }
  873. // We cannot change the share type!
  874. if ($share->getShareType() !== $originalShare->getShareType()) {
  875. throw new \InvalidArgumentException('Cannot change share type');
  876. }
  877. // We can only change the recipient on user shares
  878. if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
  879. $share->getShareType() !== IShare::TYPE_USER) {
  880. throw new \InvalidArgumentException('Can only update recipient on user shares');
  881. }
  882. // Cannot share with the owner
  883. if ($share->getShareType() === IShare::TYPE_USER &&
  884. $share->getSharedWith() === $share->getShareOwner()) {
  885. throw new \InvalidArgumentException('Cannot share with the share owner');
  886. }
  887. $this->generalCreateChecks($share);
  888. if ($share->getShareType() === IShare::TYPE_USER) {
  889. $this->userCreateChecks($share);
  890. if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
  891. //Verify the expiration date
  892. $this->validateExpirationDateInternal($share);
  893. $expirationDateUpdated = true;
  894. }
  895. } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
  896. $this->groupCreateChecks($share);
  897. if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
  898. //Verify the expiration date
  899. $this->validateExpirationDateInternal($share);
  900. $expirationDateUpdated = true;
  901. }
  902. } elseif ($share->getShareType() === IShare::TYPE_LINK
  903. || $share->getShareType() === IShare::TYPE_EMAIL) {
  904. $this->linkCreateChecks($share);
  905. // The new password is not set again if it is the same as the old
  906. // one, unless when switching from sending by Talk to sending by
  907. // mail.
  908. $plainTextPassword = $share->getPassword();
  909. $updatedPassword = $this->updateSharePasswordIfNeeded($share, $originalShare);
  910. /**
  911. * Cannot enable the getSendPasswordByTalk if there is no password set
  912. */
  913. if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
  914. throw new \InvalidArgumentException('Cannot enable sending the password by Talk with an empty password');
  915. }
  916. /**
  917. * If we're in a mail share, we need to force a password change
  918. * as either the user is not aware of the password or is already (received by mail)
  919. * Thus the SendPasswordByTalk feature would not make sense
  920. */
  921. if (!$updatedPassword && $share->getShareType() === IShare::TYPE_EMAIL) {
  922. if (!$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
  923. throw new \InvalidArgumentException('Cannot enable sending the password by Talk without setting a new password');
  924. }
  925. if ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
  926. throw new \InvalidArgumentException('Cannot disable sending the password by Talk without setting a new password');
  927. }
  928. }
  929. if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
  930. // Verify the expiration date
  931. $this->validateExpirationDateLink($share);
  932. $expirationDateUpdated = true;
  933. }
  934. } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
  935. if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
  936. //Verify the expiration date
  937. $this->validateExpirationDateInternal($share);
  938. $expirationDateUpdated = true;
  939. }
  940. }
  941. $this->pathCreateChecks($share->getNode());
  942. // Now update the share!
  943. $provider = $this->factory->getProviderForType($share->getShareType());
  944. if ($share->getShareType() === IShare::TYPE_EMAIL) {
  945. $share = $provider->update($share, $plainTextPassword);
  946. } else {
  947. $share = $provider->update($share);
  948. }
  949. if ($expirationDateUpdated === true) {
  950. \OC_Hook::emit(Share::class, 'post_set_expiration_date', [
  951. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  952. 'itemSource' => $share->getNode()->getId(),
  953. 'date' => $share->getExpirationDate(),
  954. 'uidOwner' => $share->getSharedBy(),
  955. ]);
  956. }
  957. if ($share->getPassword() !== $originalShare->getPassword()) {
  958. \OC_Hook::emit(Share::class, 'post_update_password', [
  959. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  960. 'itemSource' => $share->getNode()->getId(),
  961. 'uidOwner' => $share->getSharedBy(),
  962. 'token' => $share->getToken(),
  963. 'disabled' => is_null($share->getPassword()),
  964. ]);
  965. }
  966. if ($share->getPermissions() !== $originalShare->getPermissions()) {
  967. if ($this->userManager->userExists($share->getShareOwner())) {
  968. $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  969. } else {
  970. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  971. }
  972. \OC_Hook::emit(Share::class, 'post_update_permissions', [
  973. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  974. 'itemSource' => $share->getNode()->getId(),
  975. 'shareType' => $share->getShareType(),
  976. 'shareWith' => $share->getSharedWith(),
  977. 'uidOwner' => $share->getSharedBy(),
  978. 'permissions' => $share->getPermissions(),
  979. 'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
  980. 'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
  981. ]);
  982. }
  983. return $share;
  984. }
  985. /**
  986. * Accept a share.
  987. *
  988. * @param IShare $share
  989. * @param string $recipientId
  990. * @return IShare The share object
  991. * @throws \InvalidArgumentException
  992. * @since 9.0.0
  993. */
  994. public function acceptShare(IShare $share, string $recipientId): IShare {
  995. [$providerId,] = $this->splitFullId($share->getFullId());
  996. $provider = $this->factory->getProvider($providerId);
  997. if (!method_exists($provider, 'acceptShare')) {
  998. // TODO FIX ME
  999. throw new \InvalidArgumentException('Share provider does not support accepting');
  1000. }
  1001. $provider->acceptShare($share, $recipientId);
  1002. $event = new ShareAcceptedEvent($share);
  1003. $this->dispatcher->dispatchTyped($event);
  1004. return $share;
  1005. }
  1006. /**
  1007. * Updates the password of the given share if it is not the same as the
  1008. * password of the original share.
  1009. *
  1010. * @param IShare $share the share to update its password.
  1011. * @param IShare $originalShare the original share to compare its
  1012. * password with.
  1013. * @return boolean whether the password was updated or not.
  1014. */
  1015. private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare) {
  1016. $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword()) &&
  1017. (($share->getPassword() !== null && $originalShare->getPassword() === null) ||
  1018. ($share->getPassword() === null && $originalShare->getPassword() !== null) ||
  1019. ($share->getPassword() !== null && $originalShare->getPassword() !== null &&
  1020. !$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
  1021. // Password updated.
  1022. if ($passwordsAreDifferent) {
  1023. //Verify the password
  1024. $this->verifyPassword($share->getPassword());
  1025. // If a password is set. Hash it!
  1026. if (!empty($share->getPassword())) {
  1027. $share->setPassword($this->hasher->hash($share->getPassword()));
  1028. if ($share->getShareType() === IShare::TYPE_EMAIL) {
  1029. // Shares shared by email have temporary passwords
  1030. $this->setSharePasswordExpirationTime($share);
  1031. }
  1032. return true;
  1033. } else {
  1034. // Empty string and null are seen as NOT password protected
  1035. $share->setPassword(null);
  1036. if ($share->getShareType() === IShare::TYPE_EMAIL) {
  1037. $share->setPasswordExpirationTime(null);
  1038. }
  1039. return true;
  1040. }
  1041. } else {
  1042. // Reset the password to the original one, as it is either the same
  1043. // as the "new" password or a hashed version of it.
  1044. $share->setPassword($originalShare->getPassword());
  1045. }
  1046. return false;
  1047. }
  1048. /**
  1049. * Set the share's password expiration time
  1050. */
  1051. private function setSharePasswordExpirationTime(IShare $share): void {
  1052. if (!$this->config->getSystemValueBool('sharing.enable_mail_link_password_expiration', false)) {
  1053. // Sets password expiration date to NULL
  1054. $share->setPasswordExpirationTime();
  1055. return;
  1056. }
  1057. // Sets password expiration date
  1058. $expirationTime = null;
  1059. $now = new \DateTime();
  1060. $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
  1061. $expirationTime = $now->add(new \DateInterval('PT' . $expirationInterval . 'S'));
  1062. $share->setPasswordExpirationTime($expirationTime);
  1063. }
  1064. /**
  1065. * Delete all the children of this share
  1066. * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
  1067. *
  1068. * @param IShare $share
  1069. * @return IShare[] List of deleted shares
  1070. */
  1071. protected function deleteChildren(IShare $share) {
  1072. $deletedShares = [];
  1073. $provider = $this->factory->getProviderForType($share->getShareType());
  1074. foreach ($provider->getChildren($share) as $child) {
  1075. $this->dispatcher->dispatchTyped(new BeforeShareDeletedEvent($child));
  1076. $deletedChildren = $this->deleteChildren($child);
  1077. $deletedShares = array_merge($deletedShares, $deletedChildren);
  1078. $provider->delete($child);
  1079. $this->dispatcher->dispatchTyped(new ShareDeletedEvent($child));
  1080. $deletedShares[] = $child;
  1081. }
  1082. return $deletedShares;
  1083. }
  1084. /**
  1085. * Delete a share
  1086. *
  1087. * @param IShare $share
  1088. * @throws ShareNotFound
  1089. * @throws \InvalidArgumentException
  1090. */
  1091. public function deleteShare(IShare $share) {
  1092. try {
  1093. $share->getFullId();
  1094. } catch (\UnexpectedValueException $e) {
  1095. throw new \InvalidArgumentException('Share does not have a full id');
  1096. }
  1097. $this->dispatcher->dispatchTyped(new BeforeShareDeletedEvent($share));
  1098. // Get all children and delete them as well
  1099. $this->deleteChildren($share);
  1100. // Do the actual delete
  1101. $provider = $this->factory->getProviderForType($share->getShareType());
  1102. $provider->delete($share);
  1103. $this->dispatcher->dispatchTyped(new ShareDeletedEvent($share));
  1104. }
  1105. /**
  1106. * Unshare a file as the recipient.
  1107. * This can be different from a regular delete for example when one of
  1108. * the users in a groups deletes that share. But the provider should
  1109. * handle this.
  1110. *
  1111. * @param IShare $share
  1112. * @param string $recipientId
  1113. */
  1114. public function deleteFromSelf(IShare $share, $recipientId) {
  1115. [$providerId,] = $this->splitFullId($share->getFullId());
  1116. $provider = $this->factory->getProvider($providerId);
  1117. $provider->deleteFromSelf($share, $recipientId);
  1118. $event = new ShareDeletedFromSelfEvent($share);
  1119. $this->dispatcher->dispatchTyped($event);
  1120. }
  1121. public function restoreShare(IShare $share, string $recipientId): IShare {
  1122. [$providerId,] = $this->splitFullId($share->getFullId());
  1123. $provider = $this->factory->getProvider($providerId);
  1124. return $provider->restore($share, $recipientId);
  1125. }
  1126. /**
  1127. * @inheritdoc
  1128. */
  1129. public function moveShare(IShare $share, $recipientId) {
  1130. if ($share->getShareType() === IShare::TYPE_LINK
  1131. || $share->getShareType() === IShare::TYPE_EMAIL) {
  1132. throw new \InvalidArgumentException('Cannot change target of link share');
  1133. }
  1134. if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
  1135. throw new \InvalidArgumentException('Invalid recipient');
  1136. }
  1137. if ($share->getShareType() === IShare::TYPE_GROUP) {
  1138. $sharedWith = $this->groupManager->get($share->getSharedWith());
  1139. if (is_null($sharedWith)) {
  1140. throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
  1141. }
  1142. $recipient = $this->userManager->get($recipientId);
  1143. if (!$sharedWith->inGroup($recipient)) {
  1144. throw new \InvalidArgumentException('Invalid recipient');
  1145. }
  1146. }
  1147. [$providerId,] = $this->splitFullId($share->getFullId());
  1148. $provider = $this->factory->getProvider($providerId);
  1149. return $provider->move($share, $recipientId);
  1150. }
  1151. public function getSharesInFolder($userId, Folder $node, $reshares = false, $shallow = true) {
  1152. $providers = $this->factory->getAllProviders();
  1153. return array_reduce($providers, function ($shares, IShareProvider $provider) use ($userId, $node, $reshares, $shallow) {
  1154. $newShares = $provider->getSharesInFolder($userId, $node, $reshares, $shallow);
  1155. foreach ($newShares as $fid => $data) {
  1156. if (!isset($shares[$fid])) {
  1157. $shares[$fid] = [];
  1158. }
  1159. $shares[$fid] = array_merge($shares[$fid], $data);
  1160. }
  1161. return $shares;
  1162. }, []);
  1163. }
  1164. /**
  1165. * @inheritdoc
  1166. */
  1167. public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
  1168. if ($path !== null &&
  1169. !($path instanceof \OCP\Files\File) &&
  1170. !($path instanceof \OCP\Files\Folder)) {
  1171. throw new \InvalidArgumentException('invalid path');
  1172. }
  1173. try {
  1174. $provider = $this->factory->getProviderForType($shareType);
  1175. } catch (ProviderException $e) {
  1176. return [];
  1177. }
  1178. $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
  1179. /*
  1180. * Work around so we don't return expired shares but still follow
  1181. * proper pagination.
  1182. */
  1183. $shares2 = [];
  1184. while (true) {
  1185. $added = 0;
  1186. foreach ($shares as $share) {
  1187. try {
  1188. $this->checkShare($share);
  1189. } catch (ShareNotFound $e) {
  1190. //Ignore since this basically means the share is deleted
  1191. continue;
  1192. }
  1193. $added++;
  1194. $shares2[] = $share;
  1195. if (count($shares2) === $limit) {
  1196. break;
  1197. }
  1198. }
  1199. // If we did not fetch more shares than the limit then there are no more shares
  1200. if (count($shares) < $limit) {
  1201. break;
  1202. }
  1203. if (count($shares2) === $limit) {
  1204. break;
  1205. }
  1206. // If there was no limit on the select we are done
  1207. if ($limit === -1) {
  1208. break;
  1209. }
  1210. $offset += $added;
  1211. // Fetch again $limit shares
  1212. $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
  1213. // No more shares means we are done
  1214. if (empty($shares)) {
  1215. break;
  1216. }
  1217. }
  1218. $shares = $shares2;
  1219. return $shares;
  1220. }
  1221. /**
  1222. * @inheritdoc
  1223. */
  1224. public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
  1225. try {
  1226. $provider = $this->factory->getProviderForType($shareType);
  1227. } catch (ProviderException $e) {
  1228. return [];
  1229. }
  1230. $shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
  1231. // remove all shares which are already expired
  1232. foreach ($shares as $key => $share) {
  1233. try {
  1234. $this->checkShare($share);
  1235. } catch (ShareNotFound $e) {
  1236. unset($shares[$key]);
  1237. }
  1238. }
  1239. return $shares;
  1240. }
  1241. /**
  1242. * @inheritdoc
  1243. */
  1244. public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
  1245. $shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
  1246. // Only get deleted shares
  1247. $shares = array_filter($shares, function (IShare $share) {
  1248. return $share->getPermissions() === 0;
  1249. });
  1250. // Only get shares where the owner still exists
  1251. $shares = array_filter($shares, function (IShare $share) {
  1252. return $this->userManager->userExists($share->getShareOwner());
  1253. });
  1254. return $shares;
  1255. }
  1256. /**
  1257. * @inheritdoc
  1258. */
  1259. public function getShareById($id, $recipient = null) {
  1260. if ($id === null) {
  1261. throw new ShareNotFound();
  1262. }
  1263. [$providerId, $id] = $this->splitFullId($id);
  1264. try {
  1265. $provider = $this->factory->getProvider($providerId);
  1266. } catch (ProviderException $e) {
  1267. throw new ShareNotFound();
  1268. }
  1269. $share = $provider->getShareById($id, $recipient);
  1270. $this->checkShare($share);
  1271. return $share;
  1272. }
  1273. /**
  1274. * Get all the shares for a given path
  1275. *
  1276. * @param \OCP\Files\Node $path
  1277. * @param int $page
  1278. * @param int $perPage
  1279. *
  1280. * @return Share[]
  1281. */
  1282. public function getSharesByPath(\OCP\Files\Node $path, $page = 0, $perPage = 50) {
  1283. return [];
  1284. }
  1285. /**
  1286. * Get the share by token possible with password
  1287. *
  1288. * @param string $token
  1289. * @return IShare
  1290. *
  1291. * @throws ShareNotFound
  1292. */
  1293. public function getShareByToken($token) {
  1294. // tokens cannot be valid local user names
  1295. if ($this->userManager->userExists($token)) {
  1296. throw new ShareNotFound();
  1297. }
  1298. $share = null;
  1299. try {
  1300. if ($this->shareApiAllowLinks()) {
  1301. $provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
  1302. $share = $provider->getShareByToken($token);
  1303. }
  1304. } catch (ProviderException $e) {
  1305. } catch (ShareNotFound $e) {
  1306. }
  1307. // If it is not a link share try to fetch a federated share by token
  1308. if ($share === null) {
  1309. try {
  1310. $provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
  1311. $share = $provider->getShareByToken($token);
  1312. } catch (ProviderException $e) {
  1313. } catch (ShareNotFound $e) {
  1314. }
  1315. }
  1316. // If it is not a link share try to fetch a mail share by token
  1317. if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
  1318. try {
  1319. $provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
  1320. $share = $provider->getShareByToken($token);
  1321. } catch (ProviderException $e) {
  1322. } catch (ShareNotFound $e) {
  1323. }
  1324. }
  1325. if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
  1326. try {
  1327. $provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
  1328. $share = $provider->getShareByToken($token);
  1329. } catch (ProviderException $e) {
  1330. } catch (ShareNotFound $e) {
  1331. }
  1332. }
  1333. if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
  1334. try {
  1335. $provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
  1336. $share = $provider->getShareByToken($token);
  1337. } catch (ProviderException $e) {
  1338. } catch (ShareNotFound $e) {
  1339. }
  1340. }
  1341. if ($share === null) {
  1342. throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
  1343. }
  1344. $this->checkShare($share);
  1345. /*
  1346. * Reduce the permissions for link or email shares if public upload is not enabled
  1347. */
  1348. if (($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL)
  1349. && $share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()) {
  1350. $share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
  1351. }
  1352. return $share;
  1353. }
  1354. /**
  1355. * Check expire date and disabled owner
  1356. *
  1357. * @throws ShareNotFound
  1358. */
  1359. protected function checkShare(IShare $share): void {
  1360. if ($share->isExpired()) {
  1361. $this->deleteShare($share);
  1362. throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
  1363. }
  1364. if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
  1365. $uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
  1366. foreach ($uids as $uid) {
  1367. $user = $this->userManager->get($uid);
  1368. if ($user?->isEnabled() === false) {
  1369. throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
  1370. }
  1371. }
  1372. }
  1373. }
  1374. /**
  1375. * Verify the password of a public share
  1376. *
  1377. * @param IShare $share
  1378. * @param ?string $password
  1379. * @return bool
  1380. */
  1381. public function checkPassword(IShare $share, $password) {
  1382. // if there is no password on the share object / passsword is null, there is nothing to check
  1383. if ($password === null || $share->getPassword() === null) {
  1384. return false;
  1385. }
  1386. // Makes sure password hasn't expired
  1387. $expirationTime = $share->getPasswordExpirationTime();
  1388. if ($expirationTime !== null && $expirationTime < new \DateTime()) {
  1389. return false;
  1390. }
  1391. $newHash = '';
  1392. if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
  1393. return false;
  1394. }
  1395. if (!empty($newHash)) {
  1396. $share->setPassword($newHash);
  1397. $provider = $this->factory->getProviderForType($share->getShareType());
  1398. $provider->update($share);
  1399. }
  1400. return true;
  1401. }
  1402. /**
  1403. * @inheritdoc
  1404. */
  1405. public function userDeleted($uid) {
  1406. $types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
  1407. foreach ($types as $type) {
  1408. try {
  1409. $provider = $this->factory->getProviderForType($type);
  1410. } catch (ProviderException $e) {
  1411. continue;
  1412. }
  1413. $provider->userDeleted($uid, $type);
  1414. }
  1415. }
  1416. /**
  1417. * @inheritdoc
  1418. */
  1419. public function groupDeleted($gid) {
  1420. $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
  1421. $provider->groupDeleted($gid);
  1422. $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
  1423. if ($excludedGroups === '') {
  1424. return;
  1425. }
  1426. $excludedGroups = json_decode($excludedGroups, true);
  1427. if (json_last_error() !== JSON_ERROR_NONE) {
  1428. return;
  1429. }
  1430. $excludedGroups = array_diff($excludedGroups, [$gid]);
  1431. $this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
  1432. }
  1433. /**
  1434. * @inheritdoc
  1435. */
  1436. public function userDeletedFromGroup($uid, $gid) {
  1437. $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
  1438. $provider->userDeletedFromGroup($uid, $gid);
  1439. }
  1440. /**
  1441. * Get access list to a path. This means
  1442. * all the users that can access a given path.
  1443. *
  1444. * Consider:
  1445. * -root
  1446. * |-folder1 (23)
  1447. * |-folder2 (32)
  1448. * |-fileA (42)
  1449. *
  1450. * fileA is shared with user1 and user1@server1
  1451. * folder2 is shared with group2 (user4 is a member of group2)
  1452. * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
  1453. *
  1454. * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
  1455. * [
  1456. * users => [
  1457. * 'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
  1458. * 'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
  1459. * 'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
  1460. * ],
  1461. * remote => [
  1462. * 'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
  1463. * 'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
  1464. * ],
  1465. * public => bool
  1466. * mail => bool
  1467. * ]
  1468. *
  1469. * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
  1470. * [
  1471. * users => ['user1', 'user2', 'user4'],
  1472. * remote => bool,
  1473. * public => bool
  1474. * mail => bool
  1475. * ]
  1476. *
  1477. * This is required for encryption/activity
  1478. *
  1479. * @param \OCP\Files\Node $path
  1480. * @param bool $recursive Should we check all parent folders as well
  1481. * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
  1482. * @return array
  1483. */
  1484. public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
  1485. $owner = $path->getOwner();
  1486. if ($owner === null) {
  1487. return [];
  1488. }
  1489. $owner = $owner->getUID();
  1490. if ($currentAccess) {
  1491. $al = ['users' => [], 'remote' => [], 'public' => false];
  1492. } else {
  1493. $al = ['users' => [], 'remote' => false, 'public' => false];
  1494. }
  1495. if (!$this->userManager->userExists($owner)) {
  1496. return $al;
  1497. }
  1498. //Get node for the owner and correct the owner in case of external storage
  1499. $userFolder = $this->rootFolder->getUserFolder($owner);
  1500. if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
  1501. $nodes = $userFolder->getById($path->getId());
  1502. $path = array_shift($nodes);
  1503. if ($path === null || $path->getOwner() === null) {
  1504. return [];
  1505. }
  1506. $owner = $path->getOwner()->getUID();
  1507. }
  1508. $providers = $this->factory->getAllProviders();
  1509. /** @var Node[] $nodes */
  1510. $nodes = [];
  1511. if ($currentAccess) {
  1512. $ownerPath = $path->getPath();
  1513. $ownerPath = explode('/', $ownerPath, 4);
  1514. if (count($ownerPath) < 4) {
  1515. $ownerPath = '';
  1516. } else {
  1517. $ownerPath = $ownerPath[3];
  1518. }
  1519. $al['users'][$owner] = [
  1520. 'node_id' => $path->getId(),
  1521. 'node_path' => '/' . $ownerPath,
  1522. ];
  1523. } else {
  1524. $al['users'][] = $owner;
  1525. }
  1526. // Collect all the shares
  1527. while ($path->getPath() !== $userFolder->getPath()) {
  1528. $nodes[] = $path;
  1529. if (!$recursive) {
  1530. break;
  1531. }
  1532. $path = $path->getParent();
  1533. }
  1534. foreach ($providers as $provider) {
  1535. $tmp = $provider->getAccessList($nodes, $currentAccess);
  1536. foreach ($tmp as $k => $v) {
  1537. if (isset($al[$k])) {
  1538. if (is_array($al[$k])) {
  1539. if ($currentAccess) {
  1540. $al[$k] += $v;
  1541. } else {
  1542. $al[$k] = array_merge($al[$k], $v);
  1543. $al[$k] = array_unique($al[$k]);
  1544. $al[$k] = array_values($al[$k]);
  1545. }
  1546. } else {
  1547. $al[$k] = $al[$k] || $v;
  1548. }
  1549. } else {
  1550. $al[$k] = $v;
  1551. }
  1552. }
  1553. }
  1554. return $al;
  1555. }
  1556. /**
  1557. * Create a new share
  1558. *
  1559. * @return IShare
  1560. */
  1561. public function newShare() {
  1562. return new \OC\Share20\Share($this->rootFolder, $this->userManager);
  1563. }
  1564. /**
  1565. * Is the share API enabled
  1566. *
  1567. * @return bool
  1568. */
  1569. public function shareApiEnabled() {
  1570. return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
  1571. }
  1572. /**
  1573. * Is public link sharing enabled
  1574. *
  1575. * @return bool
  1576. */
  1577. public function shareApiAllowLinks() {
  1578. if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
  1579. return false;
  1580. }
  1581. $user = $this->userSession->getUser();
  1582. if ($user) {
  1583. $excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
  1584. if ($excludedGroups) {
  1585. $userGroups = $this->groupManager->getUserGroupIds($user);
  1586. return !(bool)array_intersect($excludedGroups, $userGroups);
  1587. }
  1588. }
  1589. return true;
  1590. }
  1591. /**
  1592. * Is password on public link requires
  1593. *
  1594. * @param bool Check group membership exclusion
  1595. * @return bool
  1596. */
  1597. public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true) {
  1598. $excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
  1599. if ($excludedGroups !== '' && $checkGroupMembership) {
  1600. $excludedGroups = json_decode($excludedGroups);
  1601. $user = $this->userSession->getUser();
  1602. if ($user) {
  1603. $userGroups = $this->groupManager->getUserGroupIds($user);
  1604. if ((bool)array_intersect($excludedGroups, $userGroups)) {
  1605. return false;
  1606. }
  1607. }
  1608. }
  1609. return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
  1610. }
  1611. /**
  1612. * Is default link expire date enabled
  1613. *
  1614. * @return bool
  1615. */
  1616. public function shareApiLinkDefaultExpireDate() {
  1617. return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
  1618. }
  1619. /**
  1620. * Is default link expire date enforced
  1621. *`
  1622. *
  1623. * @return bool
  1624. */
  1625. public function shareApiLinkDefaultExpireDateEnforced() {
  1626. return $this->shareApiLinkDefaultExpireDate() &&
  1627. $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
  1628. }
  1629. /**
  1630. * Number of default link expire days
  1631. *
  1632. * @return int
  1633. */
  1634. public function shareApiLinkDefaultExpireDays() {
  1635. return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
  1636. }
  1637. /**
  1638. * Is default internal expire date enabled
  1639. *
  1640. * @return bool
  1641. */
  1642. public function shareApiInternalDefaultExpireDate(): bool {
  1643. return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
  1644. }
  1645. /**
  1646. * Is default remote expire date enabled
  1647. *
  1648. * @return bool
  1649. */
  1650. public function shareApiRemoteDefaultExpireDate(): bool {
  1651. return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
  1652. }
  1653. /**
  1654. * Is default expire date enforced
  1655. *
  1656. * @return bool
  1657. */
  1658. public function shareApiInternalDefaultExpireDateEnforced(): bool {
  1659. return $this->shareApiInternalDefaultExpireDate() &&
  1660. $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
  1661. }
  1662. /**
  1663. * Is default expire date enforced for remote shares
  1664. *
  1665. * @return bool
  1666. */
  1667. public function shareApiRemoteDefaultExpireDateEnforced(): bool {
  1668. return $this->shareApiRemoteDefaultExpireDate() &&
  1669. $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
  1670. }
  1671. /**
  1672. * Number of default expire days
  1673. *
  1674. * @return int
  1675. */
  1676. public function shareApiInternalDefaultExpireDays(): int {
  1677. return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
  1678. }
  1679. /**
  1680. * Number of default expire days for remote shares
  1681. *
  1682. * @return int
  1683. */
  1684. public function shareApiRemoteDefaultExpireDays(): int {
  1685. return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
  1686. }
  1687. /**
  1688. * Allow public upload on link shares
  1689. *
  1690. * @return bool
  1691. */
  1692. public function shareApiLinkAllowPublicUpload() {
  1693. return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
  1694. }
  1695. /**
  1696. * check if user can only share with group members
  1697. *
  1698. * @return bool
  1699. */
  1700. public function shareWithGroupMembersOnly() {
  1701. return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
  1702. }
  1703. /**
  1704. * Check if users can share with groups
  1705. *
  1706. * @return bool
  1707. */
  1708. public function allowGroupSharing() {
  1709. return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
  1710. }
  1711. public function allowEnumeration(): bool {
  1712. return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
  1713. }
  1714. public function limitEnumerationToGroups(): bool {
  1715. return $this->allowEnumeration() &&
  1716. $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
  1717. }
  1718. public function limitEnumerationToPhone(): bool {
  1719. return $this->allowEnumeration() &&
  1720. $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
  1721. }
  1722. public function allowEnumerationFullMatch(): bool {
  1723. return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
  1724. }
  1725. public function matchEmail(): bool {
  1726. return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
  1727. }
  1728. public function ignoreSecondDisplayName(): bool {
  1729. return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
  1730. }
  1731. public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
  1732. if ($this->allowEnumerationFullMatch()) {
  1733. return true;
  1734. }
  1735. if (!$this->allowEnumeration()) {
  1736. return false;
  1737. }
  1738. if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
  1739. // Enumeration is enabled and not restricted: OK
  1740. return true;
  1741. }
  1742. if (!$currentUser instanceof IUser) {
  1743. // Enumeration restrictions require an account
  1744. return false;
  1745. }
  1746. // Enumeration is limited to phone match
  1747. if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
  1748. return true;
  1749. }
  1750. // Enumeration is limited to groups
  1751. if ($this->limitEnumerationToGroups()) {
  1752. $currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
  1753. $targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
  1754. if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
  1755. return true;
  1756. }
  1757. }
  1758. return false;
  1759. }
  1760. /**
  1761. * Copied from \OC_Util::isSharingDisabledForUser
  1762. *
  1763. * TODO: Deprecate function from OC_Util
  1764. *
  1765. * @param string $userId
  1766. * @return bool
  1767. */
  1768. public function sharingDisabledForUser($userId) {
  1769. return $this->shareDisableChecker->sharingDisabledForUser($userId);
  1770. }
  1771. /**
  1772. * @inheritdoc
  1773. */
  1774. public function outgoingServer2ServerSharesAllowed() {
  1775. return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
  1776. }
  1777. /**
  1778. * @inheritdoc
  1779. */
  1780. public function outgoingServer2ServerGroupSharesAllowed() {
  1781. return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
  1782. }
  1783. /**
  1784. * @inheritdoc
  1785. */
  1786. public function shareProviderExists($shareType) {
  1787. try {
  1788. $this->factory->getProviderForType($shareType);
  1789. } catch (ProviderException $e) {
  1790. return false;
  1791. }
  1792. return true;
  1793. }
  1794. public function registerShareProvider(string $shareProviderClass): void {
  1795. $this->factory->registerProvider($shareProviderClass);
  1796. }
  1797. public function getAllShares(): iterable {
  1798. $providers = $this->factory->getAllProviders();
  1799. foreach ($providers as $provider) {
  1800. yield from $provider->getAllShares();
  1801. }
  1802. }
  1803. }