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.

Manager.php 37KB

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