Manager.php 63 KB

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