1
0

Manager.php 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568
  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 Daniel Calviño Sánchez <danxuliu@gmail.com>
  9. * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
  10. * @author Joas Schilling <coding@schilljs.com>
  11. * @author Julius Härtl <jus@bitgrid.net>
  12. * @author Lukas Reschke <lukas@statuscode.ch>
  13. * @author Maxence Lange <maxence@artificial-owl.com>
  14. * @author Maxence Lange <maxence@nextcloud.com>
  15. * @author Morris Jobke <hey@morrisjobke.de>
  16. * @author Pauli Järvinen <pauli.jarvinen@gmail.com>
  17. * @author Robin Appelman <robin@icewind.nl>
  18. * @author Roeland Jago Douma <roeland@famdouma.nl>
  19. * @author Stephan Müller <mail@stephanmueller.eu>
  20. * @author Vincent Petry <pvince81@owncloud.com>
  21. *
  22. * @license AGPL-3.0
  23. *
  24. * This code is free software: you can redistribute it and/or modify
  25. * it under the terms of the GNU Affero General Public License, version 3,
  26. * as published by the Free Software Foundation.
  27. *
  28. * This program is distributed in the hope that it will be useful,
  29. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  31. * GNU Affero General Public License for more details.
  32. *
  33. * You should have received a copy of the GNU Affero General Public License, version 3,
  34. * along with this program. If not, see <http://www.gnu.org/licenses/>
  35. *
  36. */
  37. namespace OC\Share20;
  38. use OC\Cache\CappedMemoryCache;
  39. use OC\Files\Mount\MoveableMount;
  40. use OC\HintException;
  41. use OC\Share20\Exception\ProviderException;
  42. use OCP\Files\File;
  43. use OCP\Files\Folder;
  44. use OCP\Files\IRootFolder;
  45. use OCP\Files\Mount\IMountManager;
  46. use OCP\Files\Node;
  47. use OCP\IConfig;
  48. use OCP\IGroupManager;
  49. use OCP\IL10N;
  50. use OCP\ILogger;
  51. use OCP\IURLGenerator;
  52. use OCP\IUser;
  53. use OCP\IUserManager;
  54. use OCP\L10N\IFactory;
  55. use OCP\Mail\IMailer;
  56. use OCP\Security\IHasher;
  57. use OCP\Security\ISecureRandom;
  58. use OCP\Share\Exceptions\GenericShareException;
  59. use OCP\Share\Exceptions\ShareNotFound;
  60. use OCP\Share\IManager;
  61. use OCP\Share\IProviderFactory;
  62. use Symfony\Component\EventDispatcher\EventDispatcher;
  63. use Symfony\Component\EventDispatcher\GenericEvent;
  64. use OCP\Share\IShareProvider;
  65. use OCP\Share;
  66. /**
  67. * This class is the communication hub for all sharing related operations.
  68. */
  69. class Manager implements IManager {
  70. /** @var IProviderFactory */
  71. private $factory;
  72. /** @var ILogger */
  73. private $logger;
  74. /** @var IConfig */
  75. private $config;
  76. /** @var ISecureRandom */
  77. private $secureRandom;
  78. /** @var IHasher */
  79. private $hasher;
  80. /** @var IMountManager */
  81. private $mountManager;
  82. /** @var IGroupManager */
  83. private $groupManager;
  84. /** @var IL10N */
  85. private $l;
  86. /** @var IFactory */
  87. private $l10nFactory;
  88. /** @var IUserManager */
  89. private $userManager;
  90. /** @var IRootFolder */
  91. private $rootFolder;
  92. /** @var CappedMemoryCache */
  93. private $sharingDisabledForUsersCache;
  94. /** @var EventDispatcher */
  95. private $eventDispatcher;
  96. /** @var LegacyHooks */
  97. private $legacyHooks;
  98. /** @var IMailer */
  99. private $mailer;
  100. /** @var IURLGenerator */
  101. private $urlGenerator;
  102. /** @var \OC_Defaults */
  103. private $defaults;
  104. /**
  105. * Manager constructor.
  106. *
  107. * @param ILogger $logger
  108. * @param IConfig $config
  109. * @param ISecureRandom $secureRandom
  110. * @param IHasher $hasher
  111. * @param IMountManager $mountManager
  112. * @param IGroupManager $groupManager
  113. * @param IL10N $l
  114. * @param IFactory $l10nFactory
  115. * @param IProviderFactory $factory
  116. * @param IUserManager $userManager
  117. * @param IRootFolder $rootFolder
  118. * @param EventDispatcher $eventDispatcher
  119. * @param IMailer $mailer
  120. * @param IURLGenerator $urlGenerator
  121. * @param \OC_Defaults $defaults
  122. */
  123. public function __construct(
  124. ILogger $logger,
  125. IConfig $config,
  126. ISecureRandom $secureRandom,
  127. IHasher $hasher,
  128. IMountManager $mountManager,
  129. IGroupManager $groupManager,
  130. IL10N $l,
  131. IFactory $l10nFactory,
  132. IProviderFactory $factory,
  133. IUserManager $userManager,
  134. IRootFolder $rootFolder,
  135. EventDispatcher $eventDispatcher,
  136. IMailer $mailer,
  137. IURLGenerator $urlGenerator,
  138. \OC_Defaults $defaults
  139. ) {
  140. $this->logger = $logger;
  141. $this->config = $config;
  142. $this->secureRandom = $secureRandom;
  143. $this->hasher = $hasher;
  144. $this->mountManager = $mountManager;
  145. $this->groupManager = $groupManager;
  146. $this->l = $l;
  147. $this->l10nFactory = $l10nFactory;
  148. $this->factory = $factory;
  149. $this->userManager = $userManager;
  150. $this->rootFolder = $rootFolder;
  151. $this->eventDispatcher = $eventDispatcher;
  152. $this->sharingDisabledForUsersCache = new CappedMemoryCache();
  153. $this->legacyHooks = new LegacyHooks($this->eventDispatcher);
  154. $this->mailer = $mailer;
  155. $this->urlGenerator = $urlGenerator;
  156. $this->defaults = $defaults;
  157. }
  158. /**
  159. * Convert from a full share id to a tuple (providerId, shareId)
  160. *
  161. * @param string $id
  162. * @return string[]
  163. */
  164. private function splitFullId($id) {
  165. return explode(':', $id, 2);
  166. }
  167. /**
  168. * Verify if a password meets all requirements
  169. *
  170. * @param string $password
  171. * @throws \Exception
  172. */
  173. protected function verifyPassword($password) {
  174. if ($password === null) {
  175. // No password is set, check if this is allowed.
  176. if ($this->shareApiLinkEnforcePassword()) {
  177. throw new \InvalidArgumentException('Passwords are enforced for link shares');
  178. }
  179. return;
  180. }
  181. // Let others verify the password
  182. try {
  183. $event = new GenericEvent($password);
  184. $this->eventDispatcher->dispatch('OCP\PasswordPolicy::validate', $event);
  185. } catch (HintException $e) {
  186. throw new \Exception($e->getHint());
  187. }
  188. }
  189. /**
  190. * Check for generic requirements before creating a share
  191. *
  192. * @param \OCP\Share\IShare $share
  193. * @throws \InvalidArgumentException
  194. * @throws GenericShareException
  195. *
  196. * @suppress PhanUndeclaredClassMethod
  197. */
  198. protected function generalCreateChecks(\OCP\Share\IShare $share) {
  199. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
  200. // We expect a valid user as sharedWith for user shares
  201. if (!$this->userManager->userExists($share->getSharedWith())) {
  202. throw new \InvalidArgumentException('SharedWith is not a valid user');
  203. }
  204. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  205. // We expect a valid group as sharedWith for group shares
  206. if (!$this->groupManager->groupExists($share->getSharedWith())) {
  207. throw new \InvalidArgumentException('SharedWith is not a valid group');
  208. }
  209. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  210. if ($share->getSharedWith() !== null) {
  211. throw new \InvalidArgumentException('SharedWith should be empty');
  212. }
  213. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) {
  214. if ($share->getSharedWith() === null) {
  215. throw new \InvalidArgumentException('SharedWith should not be empty');
  216. }
  217. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
  218. if ($share->getSharedWith() === null) {
  219. throw new \InvalidArgumentException('SharedWith should not be empty');
  220. }
  221. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_CIRCLE) {
  222. $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
  223. if ($circle === null) {
  224. throw new \InvalidArgumentException('SharedWith is not a valid circle');
  225. }
  226. } else {
  227. // We can't handle other types yet
  228. throw new \InvalidArgumentException('unknown share type');
  229. }
  230. // Verify the initiator of the share is set
  231. if ($share->getSharedBy() === null) {
  232. throw new \InvalidArgumentException('SharedBy should be set');
  233. }
  234. // Cannot share with yourself
  235. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
  236. $share->getSharedWith() === $share->getSharedBy()) {
  237. throw new \InvalidArgumentException('Can’t share with yourself');
  238. }
  239. // The path should be set
  240. if ($share->getNode() === null) {
  241. throw new \InvalidArgumentException('Path should be set');
  242. }
  243. // And it should be a file or a folder
  244. if (!($share->getNode() instanceof \OCP\Files\File) &&
  245. !($share->getNode() instanceof \OCP\Files\Folder)) {
  246. throw new \InvalidArgumentException('Path should be either a file or a folder');
  247. }
  248. // And you can't share your rootfolder
  249. if ($this->userManager->userExists($share->getSharedBy())) {
  250. $sharedPath = $this->rootFolder->getUserFolder($share->getSharedBy())->getPath();
  251. } else {
  252. $sharedPath = $this->rootFolder->getUserFolder($share->getShareOwner())->getPath();
  253. }
  254. if ($sharedPath === $share->getNode()->getPath()) {
  255. throw new \InvalidArgumentException('You can’t share your root folder');
  256. }
  257. // Check if we actually have share permissions
  258. if (!$share->getNode()->isShareable()) {
  259. $message_t = $this->l->t('You are not allowed to share %s', [$share->getNode()->getPath()]);
  260. throw new GenericShareException($message_t, $message_t, 404);
  261. }
  262. // Permissions should be set
  263. if ($share->getPermissions() === null) {
  264. throw new \InvalidArgumentException('A share requires permissions');
  265. }
  266. /*
  267. * Quick fix for #23536
  268. * Non moveable mount points do not have update and delete permissions
  269. * while we 'most likely' do have that on the storage.
  270. */
  271. $permissions = $share->getNode()->getPermissions();
  272. $mount = $share->getNode()->getMountPoint();
  273. if (!($mount instanceof MoveableMount)) {
  274. $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE;
  275. }
  276. // Check that we do not share with more permissions than we have
  277. if ($share->getPermissions() & ~$permissions) {
  278. $message_t = $this->l->t('Can’t increase permissions of %s', [$share->getNode()->getPath()]);
  279. throw new GenericShareException($message_t, $message_t, 404);
  280. }
  281. // Check that read permissions are always set
  282. // Link shares are allowed to have no read permissions to allow upload to hidden folders
  283. $noReadPermissionRequired = $share->getShareType() === \OCP\Share::SHARE_TYPE_LINK
  284. || $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL;
  285. if (!$noReadPermissionRequired &&
  286. ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
  287. throw new \InvalidArgumentException('Shares need at least read permissions');
  288. }
  289. if ($share->getNode() instanceof \OCP\Files\File) {
  290. if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
  291. $message_t = $this->l->t('Files can’t be shared with delete permissions');
  292. throw new GenericShareException($message_t);
  293. }
  294. if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
  295. $message_t = $this->l->t('Files can’t be shared with create permissions');
  296. throw new GenericShareException($message_t);
  297. }
  298. }
  299. }
  300. /**
  301. * Validate if the expiration date fits the system settings
  302. *
  303. * @param \OCP\Share\IShare $share The share to validate the expiration date of
  304. * @return \OCP\Share\IShare The modified share object
  305. * @throws GenericShareException
  306. * @throws \InvalidArgumentException
  307. * @throws \Exception
  308. */
  309. protected function validateExpirationDate(\OCP\Share\IShare $share) {
  310. $expirationDate = $share->getExpirationDate();
  311. if ($expirationDate !== null) {
  312. //Make sure the expiration date is a date
  313. $expirationDate->setTime(0, 0, 0);
  314. $date = new \DateTime();
  315. $date->setTime(0, 0, 0);
  316. if ($date >= $expirationDate) {
  317. $message = $this->l->t('Expiration date is in the past');
  318. throw new GenericShareException($message, $message, 404);
  319. }
  320. }
  321. // If expiredate is empty set a default one if there is a default
  322. $fullId = null;
  323. try {
  324. $fullId = $share->getFullId();
  325. } catch (\UnexpectedValueException $e) {
  326. // This is a new share
  327. }
  328. if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
  329. $expirationDate = new \DateTime();
  330. $expirationDate->setTime(0,0,0);
  331. $expirationDate->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D'));
  332. }
  333. // If we enforce the expiration date check that is does not exceed
  334. if ($this->shareApiLinkDefaultExpireDateEnforced()) {
  335. if ($expirationDate === null) {
  336. throw new \InvalidArgumentException('Expiration date is enforced');
  337. }
  338. $date = new \DateTime();
  339. $date->setTime(0, 0, 0);
  340. $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
  341. if ($date < $expirationDate) {
  342. $message = $this->l->t('Can’t set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]);
  343. throw new GenericShareException($message, $message, 404);
  344. }
  345. }
  346. $accepted = true;
  347. $message = '';
  348. \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
  349. 'expirationDate' => &$expirationDate,
  350. 'accepted' => &$accepted,
  351. 'message' => &$message,
  352. 'passwordSet' => $share->getPassword() !== null,
  353. ]);
  354. if (!$accepted) {
  355. throw new \Exception($message);
  356. }
  357. $share->setExpirationDate($expirationDate);
  358. return $share;
  359. }
  360. /**
  361. * Check for pre share requirements for user shares
  362. *
  363. * @param \OCP\Share\IShare $share
  364. * @throws \Exception
  365. */
  366. protected function userCreateChecks(\OCP\Share\IShare $share) {
  367. // Check if we can share with group members only
  368. if ($this->shareWithGroupMembersOnly()) {
  369. $sharedBy = $this->userManager->get($share->getSharedBy());
  370. $sharedWith = $this->userManager->get($share->getSharedWith());
  371. // Verify we can share with this user
  372. $groups = array_intersect(
  373. $this->groupManager->getUserGroupIds($sharedBy),
  374. $this->groupManager->getUserGroupIds($sharedWith)
  375. );
  376. if (empty($groups)) {
  377. throw new \Exception('Sharing is only allowed with group members');
  378. }
  379. }
  380. /*
  381. * TODO: Could be costly, fix
  382. *
  383. * Also this is not what we want in the future.. then we want to squash identical shares.
  384. */
  385. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_USER);
  386. $existingShares = $provider->getSharesByPath($share->getNode());
  387. foreach($existingShares as $existingShare) {
  388. // Ignore if it is the same share
  389. try {
  390. if ($existingShare->getFullId() === $share->getFullId()) {
  391. continue;
  392. }
  393. } catch (\UnexpectedValueException $e) {
  394. //Shares are not identical
  395. }
  396. // Identical share already existst
  397. if ($existingShare->getSharedWith() === $share->getSharedWith()) {
  398. throw new \Exception('Path is already shared with this user');
  399. }
  400. // The share is already shared with this user via a group share
  401. if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  402. $group = $this->groupManager->get($existingShare->getSharedWith());
  403. if (!is_null($group)) {
  404. $user = $this->userManager->get($share->getSharedWith());
  405. if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
  406. throw new \Exception('Path is already shared with this user');
  407. }
  408. }
  409. }
  410. }
  411. }
  412. /**
  413. * Check for pre share requirements for group shares
  414. *
  415. * @param \OCP\Share\IShare $share
  416. * @throws \Exception
  417. */
  418. protected function groupCreateChecks(\OCP\Share\IShare $share) {
  419. // Verify group shares are allowed
  420. if (!$this->allowGroupSharing()) {
  421. throw new \Exception('Group sharing is now allowed');
  422. }
  423. // Verify if the user can share with this group
  424. if ($this->shareWithGroupMembersOnly()) {
  425. $sharedBy = $this->userManager->get($share->getSharedBy());
  426. $sharedWith = $this->groupManager->get($share->getSharedWith());
  427. if (is_null($sharedWith) || !$sharedWith->inGroup($sharedBy)) {
  428. throw new \Exception('Sharing is only allowed within your own groups');
  429. }
  430. }
  431. /*
  432. * TODO: Could be costly, fix
  433. *
  434. * Also this is not what we want in the future.. then we want to squash identical shares.
  435. */
  436. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
  437. $existingShares = $provider->getSharesByPath($share->getNode());
  438. foreach($existingShares as $existingShare) {
  439. try {
  440. if ($existingShare->getFullId() === $share->getFullId()) {
  441. continue;
  442. }
  443. } catch (\UnexpectedValueException $e) {
  444. //It is a new share so just continue
  445. }
  446. if ($existingShare->getSharedWith() === $share->getSharedWith()) {
  447. throw new \Exception('Path is already shared with this group');
  448. }
  449. }
  450. }
  451. /**
  452. * Check for pre share requirements for link shares
  453. *
  454. * @param \OCP\Share\IShare $share
  455. * @throws \Exception
  456. */
  457. protected function linkCreateChecks(\OCP\Share\IShare $share) {
  458. // Are link shares allowed?
  459. if (!$this->shareApiAllowLinks()) {
  460. throw new \Exception('Link sharing is not allowed');
  461. }
  462. // Link shares by definition can't have share permissions
  463. if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) {
  464. throw new \InvalidArgumentException('Link shares can’t have reshare permissions');
  465. }
  466. // Check if public upload is allowed
  467. if (!$this->shareApiLinkAllowPublicUpload() &&
  468. ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
  469. throw new \InvalidArgumentException('Public upload is not allowed');
  470. }
  471. }
  472. /**
  473. * To make sure we don't get invisible link shares we set the parent
  474. * of a link if it is a reshare. This is a quick word around
  475. * until we can properly display multiple link shares in the UI
  476. *
  477. * See: https://github.com/owncloud/core/issues/22295
  478. *
  479. * FIXME: Remove once multiple link shares can be properly displayed
  480. *
  481. * @param \OCP\Share\IShare $share
  482. */
  483. protected function setLinkParent(\OCP\Share\IShare $share) {
  484. // No sense in checking if the method is not there.
  485. if (method_exists($share, 'setParent')) {
  486. $storage = $share->getNode()->getStorage();
  487. if ($storage->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
  488. /** @var \OCA\Files_Sharing\SharedStorage $storage */
  489. $share->setParent($storage->getShareId());
  490. }
  491. }
  492. }
  493. /**
  494. * @param File|Folder $path
  495. */
  496. protected function pathCreateChecks($path) {
  497. // Make sure that we do not share a path that contains a shared mountpoint
  498. if ($path instanceof \OCP\Files\Folder) {
  499. $mounts = $this->mountManager->findIn($path->getPath());
  500. foreach($mounts as $mount) {
  501. if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
  502. throw new \InvalidArgumentException('Path contains files shared with you');
  503. }
  504. }
  505. }
  506. }
  507. /**
  508. * Check if the user that is sharing can actually share
  509. *
  510. * @param \OCP\Share\IShare $share
  511. * @throws \Exception
  512. */
  513. protected function canShare(\OCP\Share\IShare $share) {
  514. if (!$this->shareApiEnabled()) {
  515. throw new \Exception('Sharing is disabled');
  516. }
  517. if ($this->sharingDisabledForUser($share->getSharedBy())) {
  518. throw new \Exception('Sharing is disabled for you');
  519. }
  520. }
  521. /**
  522. * Share a path
  523. *
  524. * @param \OCP\Share\IShare $share
  525. * @return Share The share object
  526. * @throws \Exception
  527. *
  528. * TODO: handle link share permissions or check them
  529. */
  530. public function createShare(\OCP\Share\IShare $share) {
  531. $this->canShare($share);
  532. $this->generalCreateChecks($share);
  533. // Verify if there are any issues with the path
  534. $this->pathCreateChecks($share->getNode());
  535. /*
  536. * On creation of a share the owner is always the owner of the path
  537. * Except for mounted federated shares.
  538. */
  539. $storage = $share->getNode()->getStorage();
  540. if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
  541. $parent = $share->getNode()->getParent();
  542. while($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
  543. $parent = $parent->getParent();
  544. }
  545. $share->setShareOwner($parent->getOwner()->getUID());
  546. } else {
  547. $share->setShareOwner($share->getNode()->getOwner()->getUID());
  548. }
  549. //Verify share type
  550. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
  551. $this->userCreateChecks($share);
  552. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  553. $this->groupCreateChecks($share);
  554. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  555. $this->linkCreateChecks($share);
  556. $this->setLinkParent($share);
  557. /*
  558. * For now ignore a set token.
  559. */
  560. $share->setToken(
  561. $this->secureRandom->generate(
  562. \OC\Share\Constants::TOKEN_LENGTH,
  563. \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
  564. )
  565. );
  566. //Verify the expiration date
  567. $this->validateExpirationDate($share);
  568. //Verify the password
  569. $this->verifyPassword($share->getPassword());
  570. // If a password is set. Hash it!
  571. if ($share->getPassword() !== null) {
  572. $share->setPassword($this->hasher->hash($share->getPassword()));
  573. }
  574. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
  575. $share->setToken(
  576. $this->secureRandom->generate(
  577. \OC\Share\Constants::TOKEN_LENGTH,
  578. \OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
  579. )
  580. );
  581. }
  582. // Cannot share with the owner
  583. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
  584. $share->getSharedWith() === $share->getShareOwner()) {
  585. throw new \InvalidArgumentException('Can’t share with the share owner');
  586. }
  587. // Generate the target
  588. $target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getNode()->getName();
  589. $target = \OC\Files\Filesystem::normalizePath($target);
  590. $share->setTarget($target);
  591. // Pre share event
  592. $event = new GenericEvent($share);
  593. $a = $this->eventDispatcher->dispatch('OCP\Share::preShare', $event);
  594. if ($event->isPropagationStopped() && $event->hasArgument('error')) {
  595. throw new \Exception($event->getArgument('error'));
  596. }
  597. $oldShare = $share;
  598. $provider = $this->factory->getProviderForType($share->getShareType());
  599. $share = $provider->create($share);
  600. //reuse the node we already have
  601. $share->setNode($oldShare->getNode());
  602. // Post share event
  603. $event = new GenericEvent($share);
  604. $this->eventDispatcher->dispatch('OCP\Share::postShare', $event);
  605. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
  606. $mailSend = $share->getMailSend();
  607. if($mailSend === true) {
  608. $user = $this->userManager->get($share->getSharedWith());
  609. if ($user !== null) {
  610. $emailAddress = $user->getEMailAddress();
  611. if ($emailAddress !== null && $emailAddress !== '') {
  612. $userLang = $this->config->getUserValue($share->getSharedWith(), 'core', 'lang', null);
  613. $l = $this->l10nFactory->get('lib', $userLang);
  614. $this->sendMailNotification(
  615. $l,
  616. $share->getNode()->getName(),
  617. $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]),
  618. $share->getSharedBy(),
  619. $emailAddress,
  620. $share->getExpirationDate()
  621. );
  622. $this->logger->debug('Send share notification to ' . $emailAddress . ' for share with ID ' . $share->getId(), ['app' => 'share']);
  623. } else {
  624. $this->logger->debug('Share notification not send to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']);
  625. }
  626. } else {
  627. $this->logger->debug('Share notification not send to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']);
  628. }
  629. } else {
  630. $this->logger->debug('Share notification not send because mailsend is false.', ['app' => 'share']);
  631. }
  632. }
  633. return $share;
  634. }
  635. /**
  636. * @param IL10N $l Language of the recipient
  637. * @param string $filename file/folder name
  638. * @param string $link link to the file/folder
  639. * @param string $initiator user ID of share sender
  640. * @param string $shareWith email address of share receiver
  641. * @param \DateTime|null $expiration
  642. * @throws \Exception If mail couldn't be sent
  643. */
  644. protected function sendMailNotification(IL10N $l,
  645. $filename,
  646. $link,
  647. $initiator,
  648. $shareWith,
  649. \DateTime $expiration = null) {
  650. $initiatorUser = $this->userManager->get($initiator);
  651. $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
  652. $message = $this->mailer->createMessage();
  653. $emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [
  654. 'filename' => $filename,
  655. 'link' => $link,
  656. 'initiator' => $initiatorDisplayName,
  657. 'expiration' => $expiration,
  658. 'shareWith' => $shareWith,
  659. ]);
  660. $emailTemplate->setSubject($l->t('%s shared »%s« with you', array($initiatorDisplayName, $filename)));
  661. $emailTemplate->addHeader();
  662. $emailTemplate->addHeading($l->t('%s shared »%s« with you', [$initiatorDisplayName, $filename]), false);
  663. $text = $l->t('%s shared »%s« with you.', [$initiatorDisplayName, $filename]);
  664. $emailTemplate->addBodyText(
  665. htmlspecialchars($text . ' ' . $l->t('Click the button below to open it.')),
  666. $text
  667. );
  668. $emailTemplate->addBodyButton(
  669. $l->t('Open »%s«', [$filename]),
  670. $link
  671. );
  672. $message->setTo([$shareWith]);
  673. // The "From" contains the sharers name
  674. $instanceName = $this->defaults->getName();
  675. $senderName = $l->t(
  676. '%s via %s',
  677. [
  678. $initiatorDisplayName,
  679. $instanceName
  680. ]
  681. );
  682. $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
  683. // The "Reply-To" is set to the sharer if an mail address is configured
  684. // also the default footer contains a "Do not reply" which needs to be adjusted.
  685. $initiatorEmail = $initiatorUser->getEMailAddress();
  686. if($initiatorEmail !== null) {
  687. $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
  688. $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
  689. } else {
  690. $emailTemplate->addFooter();
  691. }
  692. $message->useTemplate($emailTemplate);
  693. $this->mailer->send($message);
  694. }
  695. /**
  696. * Update a share
  697. *
  698. * @param \OCP\Share\IShare $share
  699. * @return \OCP\Share\IShare The share object
  700. * @throws \InvalidArgumentException
  701. */
  702. public function updateShare(\OCP\Share\IShare $share) {
  703. $expirationDateUpdated = false;
  704. $this->canShare($share);
  705. try {
  706. $originalShare = $this->getShareById($share->getFullId());
  707. } catch (\UnexpectedValueException $e) {
  708. throw new \InvalidArgumentException('Share does not have a full id');
  709. }
  710. // We can't change the share type!
  711. if ($share->getShareType() !== $originalShare->getShareType()) {
  712. throw new \InvalidArgumentException('Can’t change share type');
  713. }
  714. // We can only change the recipient on user shares
  715. if ($share->getSharedWith() !== $originalShare->getSharedWith() &&
  716. $share->getShareType() !== \OCP\Share::SHARE_TYPE_USER) {
  717. throw new \InvalidArgumentException('Can only update recipient on user shares');
  718. }
  719. // Cannot share with the owner
  720. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER &&
  721. $share->getSharedWith() === $share->getShareOwner()) {
  722. throw new \InvalidArgumentException('Can’t share with the share owner');
  723. }
  724. $this->generalCreateChecks($share);
  725. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
  726. $this->userCreateChecks($share);
  727. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  728. $this->groupCreateChecks($share);
  729. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  730. $this->linkCreateChecks($share);
  731. $this->updateSharePasswordIfNeeded($share, $originalShare);
  732. if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
  733. //Verify the expiration date
  734. $this->validateExpirationDate($share);
  735. $expirationDateUpdated = true;
  736. }
  737. } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
  738. $plainTextPassword = $share->getPassword();
  739. if (!$this->updateSharePasswordIfNeeded($share, $originalShare)) {
  740. $plainTextPassword = null;
  741. }
  742. }
  743. $this->pathCreateChecks($share->getNode());
  744. // Now update the share!
  745. $provider = $this->factory->getProviderForType($share->getShareType());
  746. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) {
  747. $share = $provider->update($share, $plainTextPassword);
  748. } else {
  749. $share = $provider->update($share);
  750. }
  751. if ($expirationDateUpdated === true) {
  752. \OC_Hook::emit(Share::class, 'post_set_expiration_date', [
  753. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  754. 'itemSource' => $share->getNode()->getId(),
  755. 'date' => $share->getExpirationDate(),
  756. 'uidOwner' => $share->getSharedBy(),
  757. ]);
  758. }
  759. if ($share->getPassword() !== $originalShare->getPassword()) {
  760. \OC_Hook::emit(Share::class, 'post_update_password', [
  761. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  762. 'itemSource' => $share->getNode()->getId(),
  763. 'uidOwner' => $share->getSharedBy(),
  764. 'token' => $share->getToken(),
  765. 'disabled' => is_null($share->getPassword()),
  766. ]);
  767. }
  768. if ($share->getPermissions() !== $originalShare->getPermissions()) {
  769. if ($this->userManager->userExists($share->getShareOwner())) {
  770. $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  771. } else {
  772. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  773. }
  774. \OC_Hook::emit(Share::class, 'post_update_permissions', array(
  775. 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
  776. 'itemSource' => $share->getNode()->getId(),
  777. 'shareType' => $share->getShareType(),
  778. 'shareWith' => $share->getSharedWith(),
  779. 'uidOwner' => $share->getSharedBy(),
  780. 'permissions' => $share->getPermissions(),
  781. 'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
  782. ));
  783. }
  784. return $share;
  785. }
  786. /**
  787. * Updates the password of the given share if it is not the same as the
  788. * password of the original share.
  789. *
  790. * @param \OCP\Share\IShare $share the share to update its password.
  791. * @param \OCP\Share\IShare $originalShare the original share to compare its
  792. * password with.
  793. * @return boolean whether the password was updated or not.
  794. */
  795. private function updateSharePasswordIfNeeded(\OCP\Share\IShare $share, \OCP\Share\IShare $originalShare) {
  796. // Password updated.
  797. if ($share->getPassword() !== $originalShare->getPassword()) {
  798. //Verify the password
  799. $this->verifyPassword($share->getPassword());
  800. // If a password is set. Hash it!
  801. if ($share->getPassword() !== null) {
  802. $share->setPassword($this->hasher->hash($share->getPassword()));
  803. return true;
  804. }
  805. }
  806. return false;
  807. }
  808. /**
  809. * Delete all the children of this share
  810. * FIXME: remove once https://github.com/owncloud/core/pull/21660 is in
  811. *
  812. * @param \OCP\Share\IShare $share
  813. * @return \OCP\Share\IShare[] List of deleted shares
  814. */
  815. protected function deleteChildren(\OCP\Share\IShare $share) {
  816. $deletedShares = [];
  817. $provider = $this->factory->getProviderForType($share->getShareType());
  818. foreach ($provider->getChildren($share) as $child) {
  819. $deletedChildren = $this->deleteChildren($child);
  820. $deletedShares = array_merge($deletedShares, $deletedChildren);
  821. $provider->delete($child);
  822. $deletedShares[] = $child;
  823. }
  824. return $deletedShares;
  825. }
  826. /**
  827. * Delete a share
  828. *
  829. * @param \OCP\Share\IShare $share
  830. * @throws ShareNotFound
  831. * @throws \InvalidArgumentException
  832. */
  833. public function deleteShare(\OCP\Share\IShare $share) {
  834. try {
  835. $share->getFullId();
  836. } catch (\UnexpectedValueException $e) {
  837. throw new \InvalidArgumentException('Share does not have a full id');
  838. }
  839. $event = new GenericEvent($share);
  840. $this->eventDispatcher->dispatch('OCP\Share::preUnshare', $event);
  841. // Get all children and delete them as well
  842. $deletedShares = $this->deleteChildren($share);
  843. // Do the actual delete
  844. $provider = $this->factory->getProviderForType($share->getShareType());
  845. $provider->delete($share);
  846. // All the deleted shares caused by this delete
  847. $deletedShares[] = $share;
  848. // Emit post hook
  849. $event->setArgument('deletedShares', $deletedShares);
  850. $this->eventDispatcher->dispatch('OCP\Share::postUnshare', $event);
  851. }
  852. /**
  853. * Unshare a file as the recipient.
  854. * This can be different from a regular delete for example when one of
  855. * the users in a groups deletes that share. But the provider should
  856. * handle this.
  857. *
  858. * @param \OCP\Share\IShare $share
  859. * @param string $recipientId
  860. */
  861. public function deleteFromSelf(\OCP\Share\IShare $share, $recipientId) {
  862. list($providerId, ) = $this->splitFullId($share->getFullId());
  863. $provider = $this->factory->getProvider($providerId);
  864. $provider->deleteFromSelf($share, $recipientId);
  865. $event = new GenericEvent($share);
  866. $this->eventDispatcher->dispatch('OCP\Share::postUnshareFromSelf', $event);
  867. }
  868. /**
  869. * @inheritdoc
  870. */
  871. public function moveShare(\OCP\Share\IShare $share, $recipientId) {
  872. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
  873. throw new \InvalidArgumentException('Can’t change target of link share');
  874. }
  875. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && $share->getSharedWith() !== $recipientId) {
  876. throw new \InvalidArgumentException('Invalid recipient');
  877. }
  878. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
  879. $sharedWith = $this->groupManager->get($share->getSharedWith());
  880. if (is_null($sharedWith)) {
  881. throw new \InvalidArgumentException('Group "' . $share->getSharedWith() . '" does not exist');
  882. }
  883. $recipient = $this->userManager->get($recipientId);
  884. if (!$sharedWith->inGroup($recipient)) {
  885. throw new \InvalidArgumentException('Invalid recipient');
  886. }
  887. }
  888. list($providerId, ) = $this->splitFullId($share->getFullId());
  889. $provider = $this->factory->getProvider($providerId);
  890. $provider->move($share, $recipientId);
  891. }
  892. public function getSharesInFolder($userId, Folder $node, $reshares = false) {
  893. $providers = $this->factory->getAllProviders();
  894. return array_reduce($providers, function($shares, IShareProvider $provider) use ($userId, $node, $reshares) {
  895. $newShares = $provider->getSharesInFolder($userId, $node, $reshares);
  896. foreach ($newShares as $fid => $data) {
  897. if (!isset($shares[$fid])) {
  898. $shares[$fid] = [];
  899. }
  900. $shares[$fid] = array_merge($shares[$fid], $data);
  901. }
  902. return $shares;
  903. }, []);
  904. }
  905. /**
  906. * @inheritdoc
  907. */
  908. public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0) {
  909. if ($path !== null &&
  910. !($path instanceof \OCP\Files\File) &&
  911. !($path instanceof \OCP\Files\Folder)) {
  912. throw new \InvalidArgumentException('invalid path');
  913. }
  914. try {
  915. $provider = $this->factory->getProviderForType($shareType);
  916. } catch (ProviderException $e) {
  917. return [];
  918. }
  919. $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
  920. /*
  921. * Work around so we don't return expired shares but still follow
  922. * proper pagination.
  923. */
  924. $shares2 = [];
  925. while(true) {
  926. $added = 0;
  927. foreach ($shares as $share) {
  928. try {
  929. $this->checkExpireDate($share);
  930. } catch (ShareNotFound $e) {
  931. //Ignore since this basically means the share is deleted
  932. continue;
  933. }
  934. $added++;
  935. $shares2[] = $share;
  936. if (count($shares2) === $limit) {
  937. break;
  938. }
  939. }
  940. // If we did not fetch more shares than the limit then there are no more shares
  941. if (count($shares) < $limit) {
  942. break;
  943. }
  944. if (count($shares2) === $limit) {
  945. break;
  946. }
  947. // If there was no limit on the select we are done
  948. if ($limit === -1) {
  949. break;
  950. }
  951. $offset += $added;
  952. // Fetch again $limit shares
  953. $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
  954. // No more shares means we are done
  955. if (empty($shares)) {
  956. break;
  957. }
  958. }
  959. $shares = $shares2;
  960. return $shares;
  961. }
  962. /**
  963. * @inheritdoc
  964. */
  965. public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) {
  966. try {
  967. $provider = $this->factory->getProviderForType($shareType);
  968. } catch (ProviderException $e) {
  969. return [];
  970. }
  971. $shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
  972. // remove all shares which are already expired
  973. foreach ($shares as $key => $share) {
  974. try {
  975. $this->checkExpireDate($share);
  976. } catch (ShareNotFound $e) {
  977. unset($shares[$key]);
  978. }
  979. }
  980. return $shares;
  981. }
  982. /**
  983. * @inheritdoc
  984. */
  985. public function getShareById($id, $recipient = null) {
  986. if ($id === null) {
  987. throw new ShareNotFound();
  988. }
  989. list($providerId, $id) = $this->splitFullId($id);
  990. try {
  991. $provider = $this->factory->getProvider($providerId);
  992. } catch (ProviderException $e) {
  993. throw new ShareNotFound();
  994. }
  995. $share = $provider->getShareById($id, $recipient);
  996. $this->checkExpireDate($share);
  997. return $share;
  998. }
  999. /**
  1000. * Get all the shares for a given path
  1001. *
  1002. * @param \OCP\Files\Node $path
  1003. * @param int $page
  1004. * @param int $perPage
  1005. *
  1006. * @return Share[]
  1007. */
  1008. public function getSharesByPath(\OCP\Files\Node $path, $page=0, $perPage=50) {
  1009. return [];
  1010. }
  1011. /**
  1012. * Get the share by token possible with password
  1013. *
  1014. * @param string $token
  1015. * @return Share
  1016. *
  1017. * @throws ShareNotFound
  1018. */
  1019. public function getShareByToken($token) {
  1020. $share = null;
  1021. try {
  1022. if($this->shareApiAllowLinks()) {
  1023. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_LINK);
  1024. $share = $provider->getShareByToken($token);
  1025. }
  1026. } catch (ProviderException $e) {
  1027. } catch (ShareNotFound $e) {
  1028. }
  1029. // If it is not a link share try to fetch a federated share by token
  1030. if ($share === null) {
  1031. try {
  1032. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_REMOTE);
  1033. $share = $provider->getShareByToken($token);
  1034. } catch (ProviderException $e) {
  1035. } catch (ShareNotFound $e) {
  1036. }
  1037. }
  1038. // If it is not a link share try to fetch a mail share by token
  1039. if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_EMAIL)) {
  1040. try {
  1041. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_EMAIL);
  1042. $share = $provider->getShareByToken($token);
  1043. } catch (ProviderException $e) {
  1044. } catch (ShareNotFound $e) {
  1045. }
  1046. }
  1047. if ($share === null && $this->shareProviderExists(\OCP\Share::SHARE_TYPE_CIRCLE)) {
  1048. try {
  1049. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_CIRCLE);
  1050. $share = $provider->getShareByToken($token);
  1051. } catch (ProviderException $e) {
  1052. } catch (ShareNotFound $e) {
  1053. }
  1054. }
  1055. if ($share === null) {
  1056. throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
  1057. }
  1058. $this->checkExpireDate($share);
  1059. /*
  1060. * Reduce the permissions for link shares if public upload is not enabled
  1061. */
  1062. if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK &&
  1063. !$this->shareApiLinkAllowPublicUpload()) {
  1064. $share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
  1065. }
  1066. return $share;
  1067. }
  1068. protected function checkExpireDate($share) {
  1069. if ($share->getExpirationDate() !== null &&
  1070. $share->getExpirationDate() <= new \DateTime()) {
  1071. $this->deleteShare($share);
  1072. throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
  1073. }
  1074. }
  1075. /**
  1076. * Verify the password of a public share
  1077. *
  1078. * @param \OCP\Share\IShare $share
  1079. * @param string $password
  1080. * @return bool
  1081. */
  1082. public function checkPassword(\OCP\Share\IShare $share, $password) {
  1083. $passwordProtected = $share->getShareType() !== \OCP\Share::SHARE_TYPE_LINK
  1084. || $share->getShareType() !== \OCP\Share::SHARE_TYPE_EMAIL;
  1085. if (!$passwordProtected) {
  1086. //TODO maybe exception?
  1087. return false;
  1088. }
  1089. if ($password === null || $share->getPassword() === null) {
  1090. return false;
  1091. }
  1092. $newHash = '';
  1093. if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
  1094. return false;
  1095. }
  1096. if (!empty($newHash)) {
  1097. $share->setPassword($newHash);
  1098. $provider = $this->factory->getProviderForType($share->getShareType());
  1099. $provider->update($share);
  1100. }
  1101. return true;
  1102. }
  1103. /**
  1104. * @inheritdoc
  1105. */
  1106. public function userDeleted($uid) {
  1107. $types = [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE, \OCP\Share::SHARE_TYPE_EMAIL];
  1108. foreach ($types as $type) {
  1109. try {
  1110. $provider = $this->factory->getProviderForType($type);
  1111. } catch (ProviderException $e) {
  1112. continue;
  1113. }
  1114. $provider->userDeleted($uid, $type);
  1115. }
  1116. }
  1117. /**
  1118. * @inheritdoc
  1119. */
  1120. public function groupDeleted($gid) {
  1121. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
  1122. $provider->groupDeleted($gid);
  1123. }
  1124. /**
  1125. * @inheritdoc
  1126. */
  1127. public function userDeletedFromGroup($uid, $gid) {
  1128. $provider = $this->factory->getProviderForType(\OCP\Share::SHARE_TYPE_GROUP);
  1129. $provider->userDeletedFromGroup($uid, $gid);
  1130. }
  1131. /**
  1132. * Get access list to a path. This means
  1133. * all the users that can access a given path.
  1134. *
  1135. * Consider:
  1136. * -root
  1137. * |-folder1 (23)
  1138. * |-folder2 (32)
  1139. * |-fileA (42)
  1140. *
  1141. * fileA is shared with user1 and user1@server1
  1142. * folder2 is shared with group2 (user4 is a member of group2)
  1143. * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
  1144. *
  1145. * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
  1146. * [
  1147. * users => [
  1148. * 'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
  1149. * 'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
  1150. * 'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
  1151. * ],
  1152. * remote => [
  1153. * 'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
  1154. * 'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
  1155. * ],
  1156. * public => bool
  1157. * mail => bool
  1158. * ]
  1159. *
  1160. * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
  1161. * [
  1162. * users => ['user1', 'user2', 'user4'],
  1163. * remote => bool,
  1164. * public => bool
  1165. * mail => bool
  1166. * ]
  1167. *
  1168. * This is required for encryption/activity
  1169. *
  1170. * @param \OCP\Files\Node $path
  1171. * @param bool $recursive Should we check all parent folders as well
  1172. * @param bool $currentAccess Ensure the recipient has access to the file (e.g. did not unshare it)
  1173. * @return array
  1174. */
  1175. public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false) {
  1176. $owner = $path->getOwner()->getUID();
  1177. if ($currentAccess) {
  1178. $al = ['users' => [], 'remote' => [], 'public' => false];
  1179. } else {
  1180. $al = ['users' => [], 'remote' => false, 'public' => false];
  1181. }
  1182. if (!$this->userManager->userExists($owner)) {
  1183. return $al;
  1184. }
  1185. //Get node for the owner
  1186. $userFolder = $this->rootFolder->getUserFolder($owner);
  1187. if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
  1188. $path = $userFolder->getById($path->getId())[0];
  1189. }
  1190. $providers = $this->factory->getAllProviders();
  1191. /** @var Node[] $nodes */
  1192. $nodes = [];
  1193. if ($currentAccess) {
  1194. $ownerPath = $path->getPath();
  1195. $ownerPath = explode('/', $ownerPath, 4);
  1196. if (count($ownerPath) < 4) {
  1197. $ownerPath = '';
  1198. } else {
  1199. $ownerPath = $ownerPath[3];
  1200. }
  1201. $al['users'][$owner] = [
  1202. 'node_id' => $path->getId(),
  1203. 'node_path' => '/' . $ownerPath,
  1204. ];
  1205. } else {
  1206. $al['users'][] = $owner;
  1207. }
  1208. // Collect all the shares
  1209. while ($path->getPath() !== $userFolder->getPath()) {
  1210. $nodes[] = $path;
  1211. if (!$recursive) {
  1212. break;
  1213. }
  1214. $path = $path->getParent();
  1215. }
  1216. foreach ($providers as $provider) {
  1217. $tmp = $provider->getAccessList($nodes, $currentAccess);
  1218. foreach ($tmp as $k => $v) {
  1219. if (isset($al[$k])) {
  1220. if (is_array($al[$k])) {
  1221. if ($currentAccess) {
  1222. $al[$k] += $v;
  1223. } else {
  1224. $al[$k] = array_merge($al[$k], $v);
  1225. $al[$k] = array_unique($al[$k]);
  1226. $al[$k] = array_values($al[$k]);
  1227. }
  1228. } else {
  1229. $al[$k] = $al[$k] || $v;
  1230. }
  1231. } else {
  1232. $al[$k] = $v;
  1233. }
  1234. }
  1235. }
  1236. return $al;
  1237. }
  1238. /**
  1239. * Create a new share
  1240. * @return \OCP\Share\IShare
  1241. */
  1242. public function newShare() {
  1243. return new \OC\Share20\Share($this->rootFolder, $this->userManager);
  1244. }
  1245. /**
  1246. * Is the share API enabled
  1247. *
  1248. * @return bool
  1249. */
  1250. public function shareApiEnabled() {
  1251. return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
  1252. }
  1253. /**
  1254. * Is public link sharing enabled
  1255. *
  1256. * @return bool
  1257. */
  1258. public function shareApiAllowLinks() {
  1259. return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes';
  1260. }
  1261. /**
  1262. * Is password on public link requires
  1263. *
  1264. * @return bool
  1265. */
  1266. public function shareApiLinkEnforcePassword() {
  1267. return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes';
  1268. }
  1269. /**
  1270. * Is default expire date enabled
  1271. *
  1272. * @return bool
  1273. */
  1274. public function shareApiLinkDefaultExpireDate() {
  1275. return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes';
  1276. }
  1277. /**
  1278. * Is default expire date enforced
  1279. *`
  1280. * @return bool
  1281. */
  1282. public function shareApiLinkDefaultExpireDateEnforced() {
  1283. return $this->shareApiLinkDefaultExpireDate() &&
  1284. $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes';
  1285. }
  1286. /**
  1287. * Number of default expire days
  1288. *shareApiLinkAllowPublicUpload
  1289. * @return int
  1290. */
  1291. public function shareApiLinkDefaultExpireDays() {
  1292. return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
  1293. }
  1294. /**
  1295. * Allow public upload on link shares
  1296. *
  1297. * @return bool
  1298. */
  1299. public function shareApiLinkAllowPublicUpload() {
  1300. return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
  1301. }
  1302. /**
  1303. * check if user can only share with group members
  1304. * @return bool
  1305. */
  1306. public function shareWithGroupMembersOnly() {
  1307. return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
  1308. }
  1309. /**
  1310. * Check if users can share with groups
  1311. * @return bool
  1312. */
  1313. public function allowGroupSharing() {
  1314. return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
  1315. }
  1316. /**
  1317. * Copied from \OC_Util::isSharingDisabledForUser
  1318. *
  1319. * TODO: Deprecate fuction from OC_Util
  1320. *
  1321. * @param string $userId
  1322. * @return bool
  1323. */
  1324. public function sharingDisabledForUser($userId) {
  1325. if ($userId === null) {
  1326. return false;
  1327. }
  1328. if (isset($this->sharingDisabledForUsersCache[$userId])) {
  1329. return $this->sharingDisabledForUsersCache[$userId];
  1330. }
  1331. if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') {
  1332. $groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
  1333. $excludedGroups = json_decode($groupsList);
  1334. if (is_null($excludedGroups)) {
  1335. $excludedGroups = explode(',', $groupsList);
  1336. $newValue = json_encode($excludedGroups);
  1337. $this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue);
  1338. }
  1339. $user = $this->userManager->get($userId);
  1340. $usersGroups = $this->groupManager->getUserGroupIds($user);
  1341. if (!empty($usersGroups)) {
  1342. $remainingGroups = array_diff($usersGroups, $excludedGroups);
  1343. // if the user is only in groups which are disabled for sharing then
  1344. // sharing is also disabled for the user
  1345. if (empty($remainingGroups)) {
  1346. $this->sharingDisabledForUsersCache[$userId] = true;
  1347. return true;
  1348. }
  1349. }
  1350. }
  1351. $this->sharingDisabledForUsersCache[$userId] = false;
  1352. return false;
  1353. }
  1354. /**
  1355. * @inheritdoc
  1356. */
  1357. public function outgoingServer2ServerSharesAllowed() {
  1358. return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
  1359. }
  1360. /**
  1361. * @inheritdoc
  1362. */
  1363. public function shareProviderExists($shareType) {
  1364. try {
  1365. $this->factory->getProviderForType($shareType);
  1366. } catch (ProviderException $e) {
  1367. return false;
  1368. }
  1369. return true;
  1370. }
  1371. }