manager.php 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  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 Joas Schilling <coding@schilljs.com>
  8. * @author Lukas Reschke <lukas@statuscode.ch>
  9. * @author Roeland Jago Douma <roeland@famdouma.nl>
  10. * @author Vincent Petry <pvince81@owncloud.com>
  11. *
  12. * @license AGPL-3.0
  13. *
  14. * This code is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License, version 3,
  16. * as published by the Free Software Foundation.
  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, version 3,
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>
  25. *
  26. */
  27. namespace OC\Share20;
  28. use OC\Files\Mount\MoveableMount;
  29. use OC\HintException;
  30. use OCP\Files\IRootFolder;
  31. use OCP\Files\NotFoundException;
  32. use OCP\IUserManager;
  33. use OCP\Share\IManager;
  34. use OCP\Share\IProviderFactory;
  35. use OC\Share20\Exception\BackendError;
  36. use OCP\IConfig;
  37. use OCP\IL10N;
  38. use OCP\ILogger;
  39. use OCP\Security\ISecureRandom;
  40. use OCP\Security\IHasher;
  41. use OCP\Files\Mount\IMountManager;
  42. use OCP\IGroupManager;
  43. use OCP\Files\File;
  44. use OCP\Files\Folder;
  45. use OCP\Share\Exceptions\ShareNotFound;
  46. use OCP\Share\Exceptions\GenericShareException;
  47. use Symfony\Component\EventDispatcher\EventDispatcher;
  48. use Symfony\Component\EventDispatcher\GenericEvent;
  49. /**
  50. * This class is the communication hub for all sharing related operations.
  51. */
  52. class Manager implements IManager {
  53. /** @var IProviderFactory */
  54. private $factory;
  55. /** @var ILogger */
  56. private $logger;
  57. /** @var IConfig */
  58. private $config;
  59. /** @var ISecureRandom */
  60. private $secureRandom;
  61. /** @var IHasher */
  62. private $hasher;
  63. /** @var IMountManager */
  64. private $mountManager;
  65. /** @var IGroupManager */
  66. private $groupManager;
  67. /** @var IL10N */
  68. private $l;
  69. /** @var IUserManager */
  70. private $userManager;
  71. /** @var IRootFolder */
  72. private $rootFolder;
  73. /**
  74. * Manager constructor.
  75. *
  76. * @param ILogger $logger
  77. * @param IConfig $config
  78. * @param ISecureRandom $secureRandom
  79. * @param IHasher $hasher
  80. * @param IMountManager $mountManager
  81. * @param IGroupManager $groupManager
  82. * @param IL10N $l
  83. * @param IProviderFactory $factory
  84. * @param IUserManager $userManager
  85. * @param IRootFolder $rootFolder
  86. * @param EventDispatcher $eventDispatcher
  87. */
  88. public function __construct(
  89. ILogger $logger,
  90. IConfig $config,
  91. ISecureRandom $secureRandom,
  92. IHasher $hasher,
  93. IMountManager $mountManager,
  94. IGroupManager $groupManager,
  95. IL10N $l,
  96. IProviderFactory $factory,
  97. IUserManager $userManager,
  98. IRootFolder $rootFolder,
  99. EventDispatcher $eventDispatcher
  100. ) {
  101. $this->logger = $logger;
  102. $this->config = $config;
  103. $this->secureRandom = $secureRandom;
  104. $this->hasher = $hasher;
  105. $this->mountManager = $mountManager;
  106. $this->groupManager = $groupManager;
  107. $this->l = $l;
  108. $this->factory = $factory;
  109. $this->userManager = $userManager;
  110. $this->rootFolder = $rootFolder;
  111. $this->eventDispatcher = $eventDispatcher;
  112. }
  113. /**
  114. * Convert from a full share id to a tuple (providerId, shareId)
  115. *
  116. * @param string $id
  117. * @return string[]
  118. */
  119. private function splitFullId($id) {
  120. return explode(':', $id, 2);
  121. }
  122. /**
  123. * Verify if a password meets all requirements
  124. *
  125. * @param string $password
  126. * @throws \Exception
  127. */
  128. protected function verifyPassword($password) {
  129. if ($password === null) {
  130. // No password is set, check if this is allowed.
  131. if ($this->shareApiLinkEnforcePassword()) {
  132. throw new \InvalidArgumentException('Passwords are enforced for link shares');
  133. }
  134. return;
  135. }
  136. // Let others verify the password
  137. try {
  138. $event = new GenericEvent($password);
  139. $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
  140. } catch (HintException $e) {
  141. throw new \Exception($e->getHint());
  142. }
  143. }
  144. /**
  145. * Check for generic requirements before creating a share
  146. *
  147. * @param \OCP\Share\IShare $share
  148. * @throws \InvalidArgumentException
  149. * @throws GenericShareException
  150. */
  151. protected function generalCreateChecks(\OCP\Share\IShare $share) {
  152. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
  153. // We expect a valid user as sharedWith for user shares
  154. if (!$this->userManager->userExists($share->getSharedWith())) {
  155. throw new \InvalidArgumentException('SharedWith is not a valid user');
  156. }
  157. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  158. // We expect a valid group as sharedWith for group shares
  159. if (!$this->groupManager->groupExists($share->getSharedWith())) {
  160. throw new \InvalidArgumentException('SharedWith is not a valid group');
  161. }
  162. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  163. if ($share->getSharedWith() !== null) {
  164. throw new \InvalidArgumentException('SharedWith should be empty');
  165. }
  166. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
  167. if ($share->getSharedWith() === null) {
  168. throw new \InvalidArgumentException('SharedWith should not be empty');
  169. }
  170. } else {
  171. // We can't handle other types yet
  172. throw new \InvalidArgumentException('unkown share type');
  173. }
  174. // Verify the initiator of the share is set
  175. if ($share->getSharedBy() === null) {
  176. throw new \InvalidArgumentException('SharedBy should be set');
  177. }
  178. // Cannot share with yourself
  179. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
  180. $share->getSharedWith() === $share->getSharedBy()) {
  181. throw new \InvalidArgumentException('Can\'t share with yourself');
  182. }
  183. // The path should be set
  184. if ($share->getNode() === null) {
  185. throw new \InvalidArgumentException('Path should be set');
  186. }
  187. // And it should be a file or a folder
  188. if (!($share->getNode() instanceof \OCP\Files\File) &&
  189. !($share->getNode() instanceof \OCP\Files\Folder)) {
  190. throw new \InvalidArgumentException('Path should be either a file or a folder');
  191. }
  192. // And you can't share your rootfolder
  193. if ($this->rootFolder->getUserFolder($share->getSharedBy())->getPath() === $share->getNode()->getPath()) {
  194. throw new \InvalidArgumentException('You can\'t share your root folder');
  195. }
  196. // Check if we actually have share permissions
  197. if (!$share->getNode()->isShareable()) {
  198. $message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
  199. throw new GenericShareException($message_t, $message_t, 404);
  200. }
  201. // Permissions should be set
  202. if ($share->getPermissions() === null) {
  203. throw new \InvalidArgumentException('A share requires permissions');
  204. }
  205. /*
  206. * Quick fix for #23536
  207. * Non moveable mount points do not have update and delete permissions
  208. * while we 'most likely' do have that on the storage.
  209. */
  210. $permissions = $share->getNode()->getPermissions();
  211. $mount = $share->getNode()->getMountPoint();
  212. if (!($mount instanceof MoveableMount)) {
  213. $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
  214. }
  215. // Check that we do not share with more permissions than we have
  216. if ($share->getPermissions() & ~$permissions) {
  217. $message_t = $this->l->t('Cannot increase permissions of %s', [$share->getNode()->getPath()]);
  218. throw new GenericShareException($message_t, $message_t, 404);
  219. }
  220. // Link shares are allowed to have no read permissions to allow upload to hidden folders
  221. if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK &&
  222. ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
  223. throw new \InvalidArgumentException('Shares need at least read permissions');
  224. }
  225. }
  226. /**
  227. * Validate if the expiration date fits the system settings
  228. *
  229. * @param \OCP\Share\IShare $share The share to validate the expiration date of
  230. * @return \OCP\Share\IShare The modified share object
  231. * @throws GenericShareException
  232. * @throws \InvalidArgumentException
  233. * @throws \Exception
  234. */
  235. protected function validateExpirationDate(\OCP\Share\IShare $share) {
  236. $expirationDate = $share->getExpirationDate();
  237. if ($expirationDate !== null) {
  238. //Make sure the expiration date is a date
  239. $expirationDate->setTime(0, 0, 0);
  240. $date = new \DateTime();
  241. $date->setTime(0, 0, 0);
  242. if ($date >= $expirationDate) {
  243. $message = $this->l->t('Expiration date is in the past');
  244. throw new GenericShareException($message, $message, 404);
  245. }
  246. }
  247. // If expiredate is empty set a default one if there is a default
  248. $fullId = null;
  249. try {
  250. $fullId = $share->getFullId();
  251. } catch (\UnexpectedValueException $e) {
  252. // This is a new share
  253. }
  254. if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
  255. $expirationDate = new \DateTime();
  256. $expirationDate->setTime(0,0,0);
  257. $expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
  258. }
  259. // If we enforce the expiration date check that is does not exceed
  260. if ($this->shareApiLinkDefaultExpireDateEnforced()) {
  261. if ($expirationDate === null) {
  262. throw new \InvalidArgumentException('Expiration date is enforced');
  263. }
  264. $date = new \DateTime();
  265. $date->setTime(0, 0, 0);
  266. $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
  267. if ($date < $expirationDate) {
  268. $message = $this->l->t('Cannot set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
  269. throw new GenericShareException($message, $message, 404);
  270. }
  271. }
  272. $accepted = true;
  273. $message = '';
  274. \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
  275. 'expirationDate' => &$expirationDate,
  276. 'accepted' => &$accepted,
  277. 'message' => &$message,
  278. 'passwordSet' => $share->getPassword() !== null,
  279. ]);
  280. if (!$accepted) {
  281. throw new \Exception($message);
  282. }
  283. $share->setExpirationDate($expirationDate);
  284. return $share;
  285. }
  286. /**
  287. * Check for pre share requirements for user shares
  288. *
  289. * @param \OCP\Share\IShare $share
  290. * @throws \Exception
  291. */
  292. protected function userCreateChecks(\OCP\Share\IShare $share) {
  293. // Check if we can share with group members only
  294. if ($this->shareWithGroupMembersOnly()) {
  295. $sharedBy = $this->userManager->get($share->getSharedBy());
  296. $sharedWith = $this->userManager->get($share->getSharedWith());
  297. // Verify we can share with this user
  298. $groups = array_intersect(
  299. $this->groupManager->getUserGroupIds($sharedBy),
  300. $this->groupManager->getUserGroupIds($sharedWith)
  301. );
  302. if (empty($groups)) {
  303. throw new \Exception('Only sharing with group members is allowed');
  304. }
  305. }
  306. /*
  307. * TODO: Could be costly, fix
  308. *
  309. * Also this is not what we want in the future.. then we want to squash identical shares.
  310. */
  311. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
  312. $existingShares = $provider->getSharesByPath($share->getNode());
  313. foreach($existingShares as $existingShare) {
  314. // Ignore if it is the same share
  315. try {
  316. if ($existingShare->getFullId() === $share->getFullId()) {
  317. continue;
  318. }
  319. } catch (\UnexpectedValueException $e) {
  320. //Shares are not identical
  321. }
  322. // Identical share already existst
  323. if ($existingShare->getSharedWith() === $share->getSharedWith()) {
  324. throw new \Exception('Path already shared with this user');
  325. }
  326. // The share is already shared with this user via a group share
  327. if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  328. $group = $this->groupManager->get($existingShare->getSharedWith());
  329. $user = $this->userManager->get($share->getSharedWith());
  330. if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
  331. throw new \Exception('Path already shared with this user');
  332. }
  333. }
  334. }
  335. }
  336. /**
  337. * Check for pre share requirements for group shares
  338. *
  339. * @param \OCP\Share\IShare $share
  340. * @throws \Exception
  341. */
  342. protected function groupCreateChecks(\OCP\Share\IShare $share) {
  343. // Verify group shares are allowed
  344. if (!$this->allowGroupSharing()) {
  345. throw new \Exception('Group sharing is now allowed');
  346. }
  347. // Verify if the user can share with this group
  348. if ($this->shareWithGroupMembersOnly()) {
  349. $sharedBy = $this->userManager->get($share->getSharedBy());
  350. $sharedWith = $this->groupManager->get($share->getSharedWith());
  351. if (!$sharedWith->inGroup($sharedBy)) {
  352. throw new \Exception('Only sharing within your own groups is allowed');
  353. }
  354. }
  355. /*
  356. * TODO: Could be costly, fix
  357. *
  358. * Also this is not what we want in the future.. then we want to squash identical shares.
  359. */
  360. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
  361. $existingShares = $provider->getSharesByPath($share->getNode());
  362. foreach($existingShares as $existingShare) {
  363. try {
  364. if ($existingShare->getFullId() === $share->getFullId()) {
  365. continue;
  366. }
  367. } catch (\UnexpectedValueException $e) {
  368. //It is a new share so just continue
  369. }
  370. if ($existingShare->getSharedWith() === $share->getSharedWith()) {
  371. throw new \Exception('Path already shared with this group');
  372. }
  373. }
  374. }
  375. /**
  376. * Check for pre share requirements for link shares
  377. *
  378. * @param \OCP\Share\IShare $share
  379. * @throws \Exception
  380. */
  381. protected function linkCreateChecks(\OCP\Share\IShare $share) {
  382. // Are link shares allowed?
  383. if (!$this->shareApiAllowLinks()) {
  384. throw new \Exception('Link sharing not allowed');
  385. }
  386. // Link shares by definition can't have share permissions
  387. if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
  388. throw new \InvalidArgumentException('Link shares can\'t have reshare permissions');
  389. }
  390. // We don't allow deletion on link shares
  391. if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
  392. throw new \InvalidArgumentException('Link shares can\'t have delete permissions');
  393. }
  394. // Check if public upload is allowed
  395. if (!$this->shareApiLinkAllowPublicUpload() &&
  396. ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE))) {
  397. throw new \InvalidArgumentException('Public upload not allowed');
  398. }
  399. }
  400. /**
  401. * To make sure we don't get invisible link shares we set the parent
  402. * of a link if it is a reshare. This is a quick word around
  403. * until we can properly display multiple link shares in the UI
  404. *
  405. * See: https://github.com/owncloud/core/issues/22295
  406. *
  407. * FIXME: Remove once multiple link shares can be properly displayed
  408. *
  409. * @param \OCP\Share\IShare $share
  410. */
  411. protected function setLinkParent(\OCP\Share\IShare $share) {
  412. // No sense in checking if the method is not there.
  413. if (method_exists($share, 'setParent')) {
  414. $storage = $share->getNode()->getStorage();
  415. if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
  416. $share->setParent($storage->getShareId());
  417. }
  418. };
  419. }
  420. /**
  421. * @param File|Folder $path
  422. */
  423. protected function pathCreateChecks($path) {
  424. // Make sure that we do not share a path that contains a shared mountpoint
  425. if ($path instanceof \OCP\Files\Folder) {
  426. $mounts = $this->mountManager->findIn($path->getPath());
  427. foreach($mounts as $mount) {
  428. if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
  429. throw new \InvalidArgumentException('Path contains files shared with you');
  430. }
  431. }
  432. }
  433. }
  434. /**
  435. * Check if the user that is sharing can actually share
  436. *
  437. * @param \OCP\Share\IShare $share
  438. * @throws \Exception
  439. */
  440. protected function canShare(\OCP\Share\IShare $share) {
  441. if (!$this->shareApiEnabled()) {
  442. throw new \Exception('The share API is disabled');
  443. }
  444. if ($this->sharingDisabledForUser($share->getSharedBy())) {
  445. throw new \Exception('You are not allowed to share');
  446. }
  447. }
  448. /**
  449. * Share a path
  450. *
  451. * @param \OCP\Share\IShare $share
  452. * @return Share The share object
  453. * @throws \Exception
  454. *
  455. * TODO: handle link share permissions or check them
  456. */
  457. public function createShare(\OCP\Share\IShare $share) {
  458. $this->canShare($share);
  459. $this->generalCreateChecks($share);
  460. // Verify if there are any issues with the path
  461. $this->pathCreateChecks($share->getNode());
  462. /*
  463. * On creation of a share the owner is always the owner of the path
  464. * Except for mounted federated shares.
  465. */
  466. $storage = $share->getNode()->getStorage();
  467. if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
  468. $parent = $share->getNode()->getParent();
  469. while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
  470. $parent = $parent->getParent();
  471. }
  472. $share->setShareOwner($parent->getOwner()->getUID());
  473. } else {
  474. $share->setShareOwner($share->getNode()->getOwner()->getUID());
  475. }
  476. //Verify share type
  477. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
  478. $this->userCreateChecks($share);
  479. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  480. $this->groupCreateChecks($share);
  481. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  482. $this->linkCreateChecks($share);
  483. $this->setLinkParent($share);
  484. /*
  485. * For now ignore a set token.
  486. */
  487. $share->setToken(
  488. $this->secureRandom->generate(
  489. \OC\Share\Constants::TOKEN_LENGTH,
  490. \OCP\Security\ISecureRandom::CHAR_LOWER.
  491. \OCP\Security\ISecureRandom::CHAR_UPPER.
  492. \OCP\Security\ISecureRandom::CHAR_DIGITS
  493. )
  494. );
  495. //Verify the expiration date
  496. $this->validateExpirationDate($share);
  497. //Verify the password
  498. $this->verifyPassword($share->getPassword());
  499. // If a password is set. Hash it!
  500. if ($share->getPassword() !== null) {
  501. $share->setPassword($this->hasher->hash($share->getPassword()));
  502. }
  503. }
  504. // Cannot share with the owner
  505. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
  506. $share->getSharedWith() === $share->getShareOwner()) {
  507. throw new \InvalidArgumentException('Can\'t share with the share owner');
  508. }
  509. // Generate the target
  510. $target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
  511. $target = \OC\Files\Filesystem::normalizePath($target);
  512. $share->setTarget($target);
  513. // Pre share hook
  514. $run = true;
  515. $error = '';
  516. $preHookData = [
  517. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  518. 'itemSource' => $share->getNode()->getId(),
  519. 'shareType' => $share->getShareType(),
  520. 'uidOwner' => $share->getSharedBy(),
  521. 'permissions' => $share->getPermissions(),
  522. 'fileSource' => $share->getNode()->getId(),
  523. 'expiration' => $share->getExpirationDate(),
  524. 'token' => $share->getToken(),
  525. 'itemTarget' => $share->getTarget(),
  526. 'shareWith' => $share->getSharedWith(),
  527. 'run' => &$run,
  528. 'error' => &$error,
  529. ];
  530. \OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
  531. if ($run === false) {
  532. throw new \Exception($error);
  533. }
  534. $provider = $this->factory->getProviderForType($share->getShareType());
  535. $share = $provider->create($share);
  536. // Post share hook
  537. $postHookData = [
  538. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  539. 'itemSource' => $share->getNode()->getId(),
  540. 'shareType' => $share->getShareType(),
  541. 'uidOwner' => $share->getSharedBy(),
  542. 'permissions' => $share->getPermissions(),
  543. 'fileSource' => $share->getNode()->getId(),
  544. 'expiration' => $share->getExpirationDate(),
  545. 'token' => $share->getToken(),
  546. 'id' => $share->getId(),
  547. 'shareWith' => $share->getSharedWith(),
  548. 'itemTarget' => $share->getTarget(),
  549. 'fileTarget' => $share->getTarget(),
  550. ];
  551. \OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
  552. return $share;
  553. }
  554. /**
  555. * Update a share
  556. *
  557. * @param \OCP\Share\IShare $share
  558. * @return \OCP\Share\IShare The share object
  559. * @throws \InvalidArgumentException
  560. */
  561. public function updateShare(\OCP\Share\IShare $share) {
  562. $expirationDateUpdated = false;
  563. $this->canShare($share);
  564. try {
  565. $originalShare = $this->getShareById($share->getFullId());
  566. } catch (\UnexpectedValueException $e) {
  567. throw new \InvalidArgumentException('Share does not have a full id');
  568. }
  569. // We can't change the share type!
  570. if ($share->getShareType() !== $originalShare->getShareType()) {
  571. throw new \InvalidArgumentException('Can\'t change share type');
  572. }
  573. // We can only change the recipient on user shares
  574. if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
  575. $share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
  576. throw new \InvalidArgumentException('Can only update recipient on user shares');
  577. }
  578. // Cannot share with the owner
  579. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
  580. $share->getSharedWith() === $share->getShareOwner()) {
  581. throw new \InvalidArgumentException('Can\'t share with the share owner');
  582. }
  583. $this->generalCreateChecks($share);
  584. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
  585. $this->userCreateChecks($share);
  586. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  587. $this->groupCreateChecks($share);
  588. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  589. $this->linkCreateChecks($share);
  590. // Password updated.
  591. if ($share->getPassword() !== $originalShare->getPassword()) {
  592. //Verify the password
  593. $this->verifyPassword($share->getPassword());
  594. // If a password is set. Hash it!
  595. if ($share->getPassword() !== null) {
  596. $share->setPassword($this->hasher->hash($share->getPassword()));
  597. }
  598. }
  599. if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
  600. //Verify the expiration date
  601. $this->validateExpirationDate($share);
  602. $expirationDateUpdated = true;
  603. }
  604. }
  605. $this->pathCreateChecks($share->getNode());
  606. // Now update the share!
  607. $provider = $this->factory->getProviderForType($share->getShareType());
  608. $share = $provider->update($share);
  609. if ($expirationDateUpdated === true) {
  610. \OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
  611. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  612. 'itemSource' => $share->getNode()->getId(),
  613. 'date' => $share->getExpirationDate(),
  614. 'uidOwner' => $share->getSharedBy(),
  615. ]);
  616. }
  617. if ($share->getPassword() !== $originalShare->getPassword()) {
  618. \OC_Hook::emit('OCP\Share', 'post_update_password', [
  619. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  620. 'itemSource' => $share->getNode()->getId(),
  621. 'uidOwner' => $share->getSharedBy(),
  622. 'token' => $share->getToken(),
  623. 'disabled' => is_null($share->getPassword()),
  624. ]);
  625. }
  626. if ($share->getPermissions() !== $originalShare->getPermissions()) {
  627. $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  628. \OC_Hook::emit('OCP\Share', 'post_update_permissions', array(
  629. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  630. 'itemSource' => $share->getNode()->getId(),
  631. 'shareType' => $share->getShareType(),
  632. 'shareWith' => $share->getSharedWith(),
  633. 'uidOwner' => $share->getSharedBy(),
  634. 'permissions' => $share->getPermissions(),
  635. 'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
  636. ));
  637. }
  638. return $share;
  639. }
  640. /**
  641. * Delete all the children of this share
  642. * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
  643. *
  644. * @param \OCP\Share\IShare $share
  645. * @return \OCP\Share\IShare[] List of deleted shares
  646. */
  647. protected function deleteChildren(\OCP\Share\IShare $share) {
  648. $deletedShares = [];
  649. $provider = $this->factory->getProviderForType($share->getShareType());
  650. foreach ($provider->getChildren($share) as $child) {
  651. $deletedChildren = $this->deleteChildren($child);
  652. $deletedShares = array_merge($deletedShares, $deletedChildren);
  653. $provider->delete($child);
  654. $deletedShares[] = $child;
  655. }
  656. return $deletedShares;
  657. }
  658. /**
  659. * Delete a share
  660. *
  661. * @param \OCP\Share\IShare $share
  662. * @throws ShareNotFound
  663. * @throws \InvalidArgumentException
  664. */
  665. public function deleteShare(\OCP\Share\IShare $share) {
  666. try {
  667. $share->getFullId();
  668. } catch (\UnexpectedValueException $e) {
  669. throw new \InvalidArgumentException('Share does not have a full id');
  670. }
  671. $formatHookParams = function(\OCP\Share\IShare $share) {
  672. // Prepare hook
  673. $shareType = $share->getShareType();
  674. $sharedWith = '';
  675. if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
  676. $sharedWith = $share->getSharedWith();
  677. } else if ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
  678. $sharedWith = $share->getSharedWith();
  679. } else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
  680. $sharedWith = $share->getSharedWith();
  681. }
  682. $hookParams = [
  683. 'id' => $share->getId(),
  684. 'itemType' => $share->getNodeType(),
  685. 'itemSource' => $share->getNodeId(),
  686. 'shareType' => $shareType,
  687. 'shareWith' => $sharedWith,
  688. 'itemparent' => method_exists($share, 'getParent') ? $share->getParent() : '',
  689. 'uidOwner' => $share->getSharedBy(),
  690. 'fileSource' => $share->getNodeId(),
  691. 'fileTarget' => $share->getTarget()
  692. ];
  693. return $hookParams;
  694. };
  695. $hookParams = $formatHookParams($share);
  696. // Emit pre-hook
  697. \OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
  698. // Get all children and delete them as well
  699. $deletedShares = $this->deleteChildren($share);
  700. // Do the actual delete
  701. $provider = $this->factory->getProviderForType($share->getShareType());
  702. $provider->delete($share);
  703. // All the deleted shares caused by this delete
  704. $deletedShares[] = $share;
  705. //Format hook info
  706. $formattedDeletedShares = array_map(function($share) use ($formatHookParams) {
  707. return $formatHookParams($share);
  708. }, $deletedShares);
  709. $hookParams['deletedShares'] = $formattedDeletedShares;
  710. // Emit post hook
  711. \OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
  712. }
  713. /**
  714. * Unshare a file as the recipient.
  715. * This can be different from a regular delete for example when one of
  716. * the users in a groups deletes that share. But the provider should
  717. * handle this.
  718. *
  719. * @param \OCP\Share\IShare $share
  720. * @param string $recipientId
  721. */
  722. public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
  723. list($providerId, ) = $this->splitFullId($share->getId());
  724. $provider = $this->factory->getProvider($providerId);
  725. $provider->deleteFromSelf($share, $recipientId);
  726. }
  727. /**
  728. * @inheritdoc
  729. */
  730. public function moveShare(\OCP\Share\IShare $share, $recipientId) {
  731. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  732. throw new \InvalidArgumentException('Can\'t change target of link share');
  733. }
  734. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
  735. throw new \InvalidArgumentException('Invalid recipient');
  736. }
  737. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  738. $sharedWith = $this->groupManager->get($share->getSharedWith());
  739. $recipient = $this->userManager->get($recipientId);
  740. if (!$sharedWith->inGroup($recipient)) {
  741. throw new \InvalidArgumentException('Invalid recipient');
  742. }
  743. }
  744. list($providerId, ) = $this->splitFullId($share->getId());
  745. $provider = $this->factory->getProvider($providerId);
  746. $provider->move($share, $recipientId);
  747. }
  748. /**
  749. * Get shares shared by (initiated) by the provided user.
  750. *
  751. * @param string $userId
  752. * @param int $shareType
  753. * @param \OCP\Files\File|\OCP\Files\Folder $path
  754. * @param bool $reshares
  755. * @param int $limit The maximum number of returned results, -1 for all results
  756. * @param int $offset
  757. * @return \OCP\Share\IShare[]
  758. */
  759. public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
  760. if ($path !== null &&
  761. !($path instanceof \OCP\Files\File) &&
  762. !($path instanceof \OCP\Files\Folder)) {
  763. throw new \InvalidArgumentException('invalid path');
  764. }
  765. $provider = $this->factory->getProviderForType($shareType);
  766. $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
  767. /*
  768. * Work around so we don't return expired shares but still follow
  769. * proper pagination.
  770. */
  771. if ($shareType === \OCP\Share::SHARE_TYPE_LINK) {
  772. $shares2 = [];
  773. $today = new \DateTime();
  774. while(true) {
  775. $added = 0;
  776. foreach ($shares as $share) {
  777. // Check if the share is expired and if so delete it
  778. if ($share->getExpirationDate() !== null &&
  779. $share->getExpirationDate() <= $today
  780. ) {
  781. try {
  782. $this->deleteShare($share);
  783. } catch (NotFoundException $e) {
  784. //Ignore since this basically means the share is deleted
  785. }
  786. continue;
  787. }
  788. $added++;
  789. $shares2[] = $share;
  790. if (count($shares2) === $limit) {
  791. break;
  792. }
  793. }
  794. if (count($shares2) === $limit) {
  795. break;
  796. }
  797. // If there was no limit on the select we are done
  798. if ($limit === -1) {
  799. break;
  800. }
  801. $offset += $added;
  802. // Fetch again $limit shares
  803. $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
  804. // No more shares means we are done
  805. if (empty($shares)) {
  806. break;
  807. }
  808. }
  809. $shares = $shares2;
  810. }
  811. return $shares;
  812. }
  813. /**
  814. * @inheritdoc
  815. */
  816. public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
  817. $provider = $this->factory->getProviderForType($shareType);
  818. return $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
  819. }
  820. /**
  821. * @inheritdoc
  822. */
  823. public function getShareById($id, $recipient = null) {
  824. if ($id === null) {
  825. throw new ShareNotFound();
  826. }
  827. list($providerId, $id) = $this->splitFullId($id);
  828. $provider = $this->factory->getProvider($providerId);
  829. $share = $provider->getShareById($id, $recipient);
  830. // Validate link shares expiration date
  831. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
  832. $share->getExpirationDate() !== null &&
  833. $share->getExpirationDate() <= new \DateTime()) {
  834. $this->deleteShare($share);
  835. throw new ShareNotFound();
  836. }
  837. return $share;
  838. }
  839. /**
  840. * Get all the shares for a given path
  841. *
  842. * @param \OCP\Files\Node $path
  843. * @param int $page
  844. * @param int $perPage
  845. *
  846. * @return Share[]
  847. */
  848. public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) {
  849. }
  850. /**
  851. * Get the share by token possible with password
  852. *
  853. * @param string $token
  854. * @return Share
  855. *
  856. * @throws ShareNotFound
  857. */
  858. public function getShareByToken($token) {
  859. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
  860. try {
  861. $share = $provider->getShareByToken($token);
  862. } catch (ShareNotFound $e) {
  863. //Ignore
  864. }
  865. // If it is not a link share try to fetch a federated share by token
  866. if ($share === null) {
  867. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
  868. $share = $provider->getShareByToken($token);
  869. }
  870. if ($share->getExpirationDate() !== null &&
  871. $share->getExpirationDate() <= new \DateTime()) {
  872. $this->deleteShare($share);
  873. throw new ShareNotFound();
  874. }
  875. return $share;
  876. }
  877. /**
  878. * Verify the password of a public share
  879. *
  880. * @param \OCP\Share\IShare $share
  881. * @param string $password
  882. * @return bool
  883. */
  884. public function checkPassword(\OCP\Share\IShare $share, $password) {
  885. if ($share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK) {
  886. //TODO maybe exception?
  887. return false;
  888. }
  889. if ($password === null || $share->getPassword() === null) {
  890. return false;
  891. }
  892. $newHash = '';
  893. if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
  894. return false;
  895. }
  896. if (!empty($newHash)) {
  897. $share->setPassword($newHash);
  898. $provider = $this->factory->getProviderForType($share->getShareType());
  899. $provider->update($share);
  900. }
  901. return true;
  902. }
  903. /**
  904. * Get access list to a path. This means
  905. * all the users and groups that can access a given path.
  906. *
  907. * Consider:
  908. * -root
  909. * |-folder1
  910. * |-folder2
  911. * |-fileA
  912. *
  913. * fileA is shared with user1
  914. * folder2 is shared with group2
  915. * folder1 is shared with user2
  916. *
  917. * Then the access list will to '/folder1/folder2/fileA' is:
  918. * [
  919. * 'users' => ['user1', 'user2'],
  920. * 'groups' => ['group2']
  921. * ]
  922. *
  923. * This is required for encryption
  924. *
  925. * @param \OCP\Files\Node $path
  926. */
  927. public function getAccessList(\OCP\Files\Node $path) {
  928. }
  929. /**
  930. * Create a new share
  931. * @return \OCP\Share\IShare;
  932. */
  933. public function newShare() {
  934. return new \OC\Share20\Share($this->rootFolder);
  935. }
  936. /**
  937. * Is the share API enabled
  938. *
  939. * @return bool
  940. */
  941. public function shareApiEnabled() {
  942. return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
  943. }
  944. /**
  945. * Is public link sharing enabled
  946. *
  947. * @return bool
  948. */
  949. public function shareApiAllowLinks() {
  950. return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
  951. }
  952. /**
  953. * Is password on public link requires
  954. *
  955. * @return bool
  956. */
  957. public function shareApiLinkEnforcePassword() {
  958. return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
  959. }
  960. /**
  961. * Is default expire date enabled
  962. *
  963. * @return bool
  964. */
  965. public function shareApiLinkDefaultExpireDate() {
  966. return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
  967. }
  968. /**
  969. * Is default expire date enforced
  970. *`
  971. * @return bool
  972. */
  973. public function shareApiLinkDefaultExpireDateEnforced() {
  974. return $this->shareApiLinkDefaultExpireDate() &&
  975. $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
  976. }
  977. /**
  978. * Number of default expire days
  979. *shareApiLinkAllowPublicUpload
  980. * @return int
  981. */
  982. public function shareApiLinkDefaultExpireDays() {
  983. return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
  984. }
  985. /**
  986. * Allow public upload on link shares
  987. *
  988. * @return bool
  989. */
  990. public function shareApiLinkAllowPublicUpload() {
  991. return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
  992. }
  993. /**
  994. * check if user can only share with group members
  995. * @return bool
  996. */
  997. public function shareWithGroupMembersOnly() {
  998. return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
  999. }
  1000. /**
  1001. * Check if users can share with groups
  1002. * @return bool
  1003. */
  1004. public function allowGroupSharing() {
  1005. return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
  1006. }
  1007. /**
  1008. * Copied from \OC_Util::isSharingDisabledForUser
  1009. *
  1010. * TODO: Deprecate fuction from OC_Util
  1011. *
  1012. * @param string $userId
  1013. * @return bool
  1014. */
  1015. public function sharingDisabledForUser($userId) {
  1016. if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
  1017. $groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
  1018. $excludedGroups = json_decode($groupsList);
  1019. if (is_null($excludedGroups)) {
  1020. $excludedGroups = explode(',', $groupsList);
  1021. $newValue = json_encode($excludedGroups);
  1022. $this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
  1023. }
  1024. $user = $this->userManager->get($userId);
  1025. $usersGroups = $this->groupManager->getUserGroupIds($user);
  1026. if (!empty($usersGroups)) {
  1027. $remainingGroups = array_diff($usersGroups, $excludedGroups);
  1028. // if the user is only in groups which are disabled for sharing then
  1029. // sharing is also disabled for the user
  1030. if (empty($remainingGroups)) {
  1031. return true;
  1032. }
  1033. }
  1034. }
  1035. return false;
  1036. }
  1037. /**
  1038. * @inheritdoc
  1039. */
  1040. public function outgoingServer2ServerSharesAllowed() {
  1041. return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
  1042. }
  1043. }