Manager.php 62 KB

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