ShareAPIController.php 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016, ownCloud, Inc.
  5. *
  6. * @author Bjoern Schiessle <bjoern@schiessle.org>
  7. * @author castillo92 <37965565+castillo92@users.noreply.github.com>
  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 Gary Kim <gary@garykim.dev>
  12. * @author Georg Ehrke <oc.list@georgehrke.com>
  13. * @author Joas Schilling <coding@schilljs.com>
  14. * @author John Molakvoæ <skjnldsv@protonmail.com>
  15. * @author Julius Härtl <jus@bitgrid.net>
  16. * @author Lukas Reschke <lukas@statuscode.ch>
  17. * @author Maxence Lange <maxence@artificial-owl.com>
  18. * @author Maxence Lange <maxence@nextcloud.com>
  19. * @author Michael Jobst <mjobst+github@tecratech.de>
  20. * @author Morris Jobke <hey@morrisjobke.de>
  21. * @author Richard Steinmetz <richard@steinmetz.cloud>
  22. * @author Robin Appelman <robin@icewind.nl>
  23. * @author Roeland Jago Douma <roeland@famdouma.nl>
  24. * @author Valdnet <47037905+Valdnet@users.noreply.github.com>
  25. * @author Vincent Petry <vincent@nextcloud.com>
  26. * @author waleczny <michal@walczak.xyz>
  27. *
  28. * @license AGPL-3.0
  29. *
  30. * This code is free software: you can redistribute it and/or modify
  31. * it under the terms of the GNU Affero General Public License, version 3,
  32. * as published by the Free Software Foundation.
  33. *
  34. * This program is distributed in the hope that it will be useful,
  35. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  36. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  37. * GNU Affero General Public License for more details.
  38. *
  39. * You should have received a copy of the GNU Affero General Public License, version 3,
  40. * along with this program. If not, see <http://www.gnu.org/licenses/>
  41. *
  42. */
  43. namespace OCA\Files_Sharing\Controller;
  44. use OC\Files\FileInfo;
  45. use OC\Files\Storage\Wrapper\Wrapper;
  46. use OCA\Files_Sharing\Exceptions\SharingRightsException;
  47. use OCA\Files_Sharing\External\Storage;
  48. use OCA\Files_Sharing\SharedStorage;
  49. use OCA\Files\Helper;
  50. use OCP\App\IAppManager;
  51. use OCP\AppFramework\Http\DataResponse;
  52. use OCP\AppFramework\OCS\OCSBadRequestException;
  53. use OCP\AppFramework\OCS\OCSException;
  54. use OCP\AppFramework\OCS\OCSForbiddenException;
  55. use OCP\AppFramework\OCS\OCSNotFoundException;
  56. use OCP\AppFramework\OCSController;
  57. use OCP\AppFramework\QueryException;
  58. use OCP\Constants;
  59. use OCP\Files\InvalidPathException;
  60. use OCP\Files\IRootFolder;
  61. use OCP\Files\Folder;
  62. use OCP\Files\Node;
  63. use OCP\Files\NotFoundException;
  64. use OCP\IConfig;
  65. use OCP\IGroupManager;
  66. use OCP\IL10N;
  67. use OCP\IPreview;
  68. use OCP\IRequest;
  69. use OCP\IServerContainer;
  70. use OCP\IURLGenerator;
  71. use OCP\IUserManager;
  72. use OCP\Lock\ILockingProvider;
  73. use OCP\Lock\LockedException;
  74. use OCP\Share;
  75. use OCP\Share\Exceptions\GenericShareException;
  76. use OCP\Share\Exceptions\ShareNotFound;
  77. use OCP\Share\IManager;
  78. use OCP\Share\IShare;
  79. use OCP\UserStatus\IManager as IUserStatusManager;
  80. /**
  81. * Class Share20OCS
  82. *
  83. * @package OCA\Files_Sharing\API
  84. */
  85. class ShareAPIController extends OCSController {
  86. /** @var IManager */
  87. private $shareManager;
  88. /** @var IGroupManager */
  89. private $groupManager;
  90. /** @var IUserManager */
  91. private $userManager;
  92. /** @var IRootFolder */
  93. private $rootFolder;
  94. /** @var IURLGenerator */
  95. private $urlGenerator;
  96. /** @var string */
  97. private $currentUser;
  98. /** @var IL10N */
  99. private $l;
  100. /** @var \OCP\Files\Node */
  101. private $lockedNode;
  102. /** @var IConfig */
  103. private $config;
  104. /** @var IAppManager */
  105. private $appManager;
  106. /** @var IServerContainer */
  107. private $serverContainer;
  108. /** @var IUserStatusManager */
  109. private $userStatusManager;
  110. /** @var IPreview */
  111. private $previewManager;
  112. /**
  113. * Share20OCS constructor.
  114. *
  115. * @param string $appName
  116. * @param IRequest $request
  117. * @param IManager $shareManager
  118. * @param IGroupManager $groupManager
  119. * @param IUserManager $userManager
  120. * @param IRootFolder $rootFolder
  121. * @param IURLGenerator $urlGenerator
  122. * @param string $userId
  123. * @param IL10N $l10n
  124. * @param IConfig $config
  125. * @param IAppManager $appManager
  126. * @param IServerContainer $serverContainer
  127. * @param IUserStatusManager $userStatusManager
  128. */
  129. public function __construct(
  130. string $appName,
  131. IRequest $request,
  132. IManager $shareManager,
  133. IGroupManager $groupManager,
  134. IUserManager $userManager,
  135. IRootFolder $rootFolder,
  136. IURLGenerator $urlGenerator,
  137. string $userId = null,
  138. IL10N $l10n,
  139. IConfig $config,
  140. IAppManager $appManager,
  141. IServerContainer $serverContainer,
  142. IUserStatusManager $userStatusManager,
  143. IPreview $previewManager
  144. ) {
  145. parent::__construct($appName, $request);
  146. $this->shareManager = $shareManager;
  147. $this->userManager = $userManager;
  148. $this->groupManager = $groupManager;
  149. $this->request = $request;
  150. $this->rootFolder = $rootFolder;
  151. $this->urlGenerator = $urlGenerator;
  152. $this->currentUser = $userId;
  153. $this->l = $l10n;
  154. $this->config = $config;
  155. $this->appManager = $appManager;
  156. $this->serverContainer = $serverContainer;
  157. $this->userStatusManager = $userStatusManager;
  158. $this->previewManager = $previewManager;
  159. }
  160. /**
  161. * Convert an IShare to an array for OCS output
  162. *
  163. * @param \OCP\Share\IShare $share
  164. * @param Node|null $recipientNode
  165. * @return array
  166. * @throws NotFoundException In case the node can't be resolved.
  167. *
  168. * @suppress PhanUndeclaredClassMethod
  169. */
  170. protected function formatShare(IShare $share, Node $recipientNode = null): array {
  171. $sharedBy = $this->userManager->get($share->getSharedBy());
  172. $shareOwner = $this->userManager->get($share->getShareOwner());
  173. $result = [
  174. 'id' => $share->getId(),
  175. 'share_type' => $share->getShareType(),
  176. 'uid_owner' => $share->getSharedBy(),
  177. 'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
  178. // recipient permissions
  179. 'permissions' => $share->getPermissions(),
  180. // current user permissions on this share
  181. 'can_edit' => $this->canEditShare($share),
  182. 'can_delete' => $this->canDeleteShare($share),
  183. 'stime' => $share->getShareTime()->getTimestamp(),
  184. 'parent' => null,
  185. 'expiration' => null,
  186. 'token' => null,
  187. 'uid_file_owner' => $share->getShareOwner(),
  188. 'note' => $share->getNote(),
  189. 'label' => $share->getLabel(),
  190. 'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
  191. ];
  192. $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
  193. if ($recipientNode) {
  194. $node = $recipientNode;
  195. } else {
  196. $nodes = $userFolder->getById($share->getNodeId());
  197. if (empty($nodes)) {
  198. // fallback to guessing the path
  199. $node = $userFolder->get($share->getTarget());
  200. if ($node === null || $share->getTarget() === '') {
  201. throw new NotFoundException();
  202. }
  203. } else {
  204. $node = reset($nodes);
  205. }
  206. }
  207. $result['path'] = $userFolder->getRelativePath($node->getPath());
  208. if ($node instanceof Folder) {
  209. $result['item_type'] = 'folder';
  210. } else {
  211. $result['item_type'] = 'file';
  212. }
  213. $result['mimetype'] = $node->getMimetype();
  214. $result['has_preview'] = $this->previewManager->isAvailable($node);
  215. $result['storage_id'] = $node->getStorage()->getId();
  216. $result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
  217. $result['item_source'] = $node->getId();
  218. $result['file_source'] = $node->getId();
  219. $result['file_parent'] = $node->getParent()->getId();
  220. $result['file_target'] = $share->getTarget();
  221. $expiration = $share->getExpirationDate();
  222. if ($expiration !== null) {
  223. $result['expiration'] = $expiration->format('Y-m-d 00:00:00');
  224. }
  225. if ($share->getShareType() === IShare::TYPE_USER) {
  226. $sharedWith = $this->userManager->get($share->getSharedWith());
  227. $result['share_with'] = $share->getSharedWith();
  228. $result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
  229. $result['share_with_displayname_unique'] = $sharedWith !== null ? (
  230. !empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
  231. ) : $share->getSharedWith();
  232. $result['status'] = [];
  233. $userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
  234. $userStatus = array_shift($userStatuses);
  235. if ($userStatus) {
  236. $result['status'] = [
  237. 'status' => $userStatus->getStatus(),
  238. 'message' => $userStatus->getMessage(),
  239. 'icon' => $userStatus->getIcon(),
  240. 'clearAt' => $userStatus->getClearAt()
  241. ? (int)$userStatus->getClearAt()->format('U')
  242. : null,
  243. ];
  244. }
  245. } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
  246. $group = $this->groupManager->get($share->getSharedWith());
  247. $result['share_with'] = $share->getSharedWith();
  248. $result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
  249. } elseif ($share->getShareType() === IShare::TYPE_LINK) {
  250. // "share_with" and "share_with_displayname" for passwords of link
  251. // shares was deprecated in Nextcloud 15, use "password" instead.
  252. $result['share_with'] = $share->getPassword();
  253. $result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
  254. $result['password'] = $share->getPassword();
  255. $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
  256. $result['token'] = $share->getToken();
  257. $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
  258. } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
  259. $result['share_with'] = $share->getSharedWith();
  260. $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
  261. $result['token'] = $share->getToken();
  262. } elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
  263. $result['share_with'] = $share->getSharedWith();
  264. $result['password'] = $share->getPassword();
  265. $result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
  266. $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
  267. $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
  268. $result['token'] = $share->getToken();
  269. } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
  270. // getSharedWith() returns either "name (type, owner)" or
  271. // "name (type, owner) [id]", depending on the Circles app version.
  272. $hasCircleId = (substr($share->getSharedWith(), -1) === ']');
  273. $result['share_with_displayname'] = $share->getSharedWithDisplayName();
  274. if (empty($result['share_with_displayname'])) {
  275. $displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
  276. $result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
  277. }
  278. $result['share_with_avatar'] = $share->getSharedWithAvatar();
  279. $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
  280. $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
  281. if (is_bool($shareWithLength)) {
  282. $shareWithLength = -1;
  283. }
  284. $result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
  285. } elseif ($share->getShareType() === IShare::TYPE_ROOM) {
  286. $result['share_with'] = $share->getSharedWith();
  287. $result['share_with_displayname'] = '';
  288. try {
  289. $result = array_merge($result, $this->getRoomShareHelper()->formatShare($share));
  290. } catch (QueryException $e) {
  291. }
  292. } elseif ($share->getShareType() === IShare::TYPE_DECK) {
  293. $result['share_with'] = $share->getSharedWith();
  294. $result['share_with_displayname'] = '';
  295. try {
  296. $result = array_merge($result, $this->getDeckShareHelper()->formatShare($share));
  297. } catch (QueryException $e) {
  298. }
  299. } elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
  300. $result['share_with'] = $share->getSharedWith();
  301. $result['share_with_displayname'] = '';
  302. try {
  303. $result = array_merge($result, $this->getSciencemeshShareHelper()->formatShare($share));
  304. } catch (QueryException $e) {
  305. }
  306. }
  307. $result['mail_send'] = $share->getMailSend() ? 1 : 0;
  308. $result['hide_download'] = $share->getHideDownload() ? 1 : 0;
  309. $result['attributes'] = null;
  310. if ($attributes = $share->getAttributes()) {
  311. $result['attributes'] = \json_encode($attributes->toArray());
  312. }
  313. return $result;
  314. }
  315. /**
  316. * Check if one of the users address books knows the exact property, if
  317. * yes we return the full name.
  318. *
  319. * @param string $query
  320. * @param string $property
  321. * @return string
  322. */
  323. private function getDisplayNameFromAddressBook(string $query, string $property): string {
  324. // FIXME: If we inject the contacts manager it gets initialized before any address books are registered
  325. $result = \OC::$server->getContactsManager()->search($query, [$property], [
  326. 'limit' => 1,
  327. 'enumeration' => false,
  328. 'strict_search' => true,
  329. ]);
  330. foreach ($result as $r) {
  331. foreach ($r[$property] as $value) {
  332. if ($value === $query && $r['FN']) {
  333. return $r['FN'];
  334. }
  335. }
  336. }
  337. return $query;
  338. }
  339. /**
  340. * Get a specific share by id
  341. *
  342. * @NoAdminRequired
  343. *
  344. * @param string $id
  345. * @param bool $includeTags
  346. * @return DataResponse
  347. * @throws OCSNotFoundException
  348. */
  349. public function getShare(string $id, bool $includeTags = false): DataResponse {
  350. try {
  351. $share = $this->getShareById($id);
  352. } catch (ShareNotFound $e) {
  353. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  354. }
  355. try {
  356. if ($this->canAccessShare($share)) {
  357. $share = $this->formatShare($share);
  358. if ($includeTags) {
  359. $share = Helper::populateTags([$share], 'file_source', \OC::$server->getTagManager());
  360. } else {
  361. $share = [$share];
  362. }
  363. return new DataResponse($share);
  364. }
  365. } catch (NotFoundException $e) {
  366. // Fall through
  367. }
  368. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  369. }
  370. /**
  371. * Delete a share
  372. *
  373. * @NoAdminRequired
  374. *
  375. * @param string $id
  376. * @return DataResponse
  377. * @throws OCSNotFoundException
  378. */
  379. public function deleteShare(string $id): DataResponse {
  380. try {
  381. $share = $this->getShareById($id);
  382. } catch (ShareNotFound $e) {
  383. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  384. }
  385. try {
  386. $this->lock($share->getNode());
  387. } catch (LockedException $e) {
  388. throw new OCSNotFoundException($this->l->t('Could not delete share'));
  389. }
  390. if (!$this->canAccessShare($share)) {
  391. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  392. }
  393. // if it's a group share or a room share
  394. // we don't delete the share, but only the
  395. // mount point. Allowing it to be restored
  396. // from the deleted shares
  397. if ($this->canDeleteShareFromSelf($share)) {
  398. $this->shareManager->deleteFromSelf($share, $this->currentUser);
  399. } else {
  400. if (!$this->canDeleteShare($share)) {
  401. throw new OCSForbiddenException($this->l->t('Could not delete share'));
  402. }
  403. $this->shareManager->deleteShare($share);
  404. }
  405. return new DataResponse();
  406. }
  407. /**
  408. * @NoAdminRequired
  409. *
  410. * @param string $path
  411. * @param int $permissions
  412. * @param int $shareType
  413. * @param string $shareWith
  414. * @param string $publicUpload
  415. * @param string $password
  416. * @param string $sendPasswordByTalk
  417. * @param string $expireDate
  418. * @param string $label
  419. * @param string $attributes
  420. *
  421. * @return DataResponse
  422. * @throws NotFoundException
  423. * @throws OCSBadRequestException
  424. * @throws OCSException
  425. * @throws OCSForbiddenException
  426. * @throws OCSNotFoundException
  427. * @throws InvalidPathException
  428. * @suppress PhanUndeclaredClassMethod
  429. */
  430. public function createShare(
  431. string $path = null,
  432. int $permissions = null,
  433. int $shareType = -1,
  434. string $shareWith = null,
  435. string $publicUpload = 'false',
  436. string $password = '',
  437. string $sendPasswordByTalk = null,
  438. string $expireDate = '',
  439. string $note = '',
  440. string $label = '',
  441. string $attributes = null
  442. ): DataResponse {
  443. $share = $this->shareManager->newShare();
  444. if ($permissions === null) {
  445. $permissions = $this->config->getAppValue('core', 'shareapi_default_permissions', Constants::PERMISSION_ALL);
  446. }
  447. // Verify path
  448. if ($path === null) {
  449. throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
  450. }
  451. $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
  452. try {
  453. /** @var \OC\Files\Node\Node $node */
  454. $node = $userFolder->get($path);
  455. } catch (NotFoundException $e) {
  456. throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
  457. }
  458. // a user can have access to a file through different paths, with differing permissions
  459. // combine all permissions to determine if the user can share this file
  460. $nodes = $userFolder->getById($node->getId());
  461. foreach ($nodes as $nodeById) {
  462. /** @var FileInfo $fileInfo */
  463. $fileInfo = $node->getFileInfo();
  464. $fileInfo['permissions'] |= $nodeById->getPermissions();
  465. }
  466. $share->setNode($node);
  467. try {
  468. $this->lock($share->getNode());
  469. } catch (LockedException $e) {
  470. throw new OCSNotFoundException($this->l->t('Could not create share'));
  471. }
  472. if ($permissions < 0 || $permissions > Constants::PERMISSION_ALL) {
  473. throw new OCSNotFoundException($this->l->t('Invalid permissions'));
  474. }
  475. // Shares always require read permissions
  476. $permissions |= Constants::PERMISSION_READ;
  477. if ($node instanceof \OCP\Files\File) {
  478. // Single file shares should never have delete or create permissions
  479. $permissions &= ~Constants::PERMISSION_DELETE;
  480. $permissions &= ~Constants::PERMISSION_CREATE;
  481. }
  482. /**
  483. * Hack for https://github.com/owncloud/core/issues/22587
  484. * We check the permissions via webdav. But the permissions of the mount point
  485. * do not equal the share permissions. Here we fix that for federated mounts.
  486. */
  487. if ($node->getStorage()->instanceOfStorage(Storage::class)) {
  488. $permissions &= ~($permissions & ~$node->getPermissions());
  489. }
  490. if ($attributes !== null) {
  491. $share = $this->setShareAttributes($share, $attributes);
  492. }
  493. $share->setSharedBy($this->currentUser);
  494. $this->checkInheritedAttributes($share);
  495. if ($shareType === IShare::TYPE_USER) {
  496. // Valid user is required to share
  497. if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
  498. throw new OCSNotFoundException($this->l->t('Please specify a valid user'));
  499. }
  500. $share->setSharedWith($shareWith);
  501. $share->setPermissions($permissions);
  502. } elseif ($shareType === IShare::TYPE_GROUP) {
  503. if (!$this->shareManager->allowGroupSharing()) {
  504. throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
  505. }
  506. // Valid group is required to share
  507. if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
  508. throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
  509. }
  510. $share->setSharedWith($shareWith);
  511. $share->setPermissions($permissions);
  512. } elseif ($shareType === IShare::TYPE_LINK
  513. || $shareType === IShare::TYPE_EMAIL) {
  514. // Can we even share links?
  515. if (!$this->shareManager->shareApiAllowLinks()) {
  516. throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
  517. }
  518. if ($publicUpload === 'true') {
  519. // Check if public upload is allowed
  520. if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
  521. throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
  522. }
  523. // Public upload can only be set for folders
  524. if ($node instanceof \OCP\Files\File) {
  525. throw new OCSNotFoundException($this->l->t('Public upload is only possible for publicly shared folders'));
  526. }
  527. $permissions = Constants::PERMISSION_READ |
  528. Constants::PERMISSION_CREATE |
  529. Constants::PERMISSION_UPDATE |
  530. Constants::PERMISSION_DELETE;
  531. } else {
  532. $permissions = Constants::PERMISSION_READ;
  533. }
  534. // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
  535. if (($permissions & Constants::PERMISSION_READ) && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
  536. $permissions |= Constants::PERMISSION_SHARE;
  537. }
  538. $share->setPermissions($permissions);
  539. // Set password
  540. if ($password !== '') {
  541. $share->setPassword($password);
  542. }
  543. // Only share by mail have a recipient
  544. if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
  545. $share->setSharedWith($shareWith);
  546. }
  547. // If we have a label, use it
  548. if (!empty($label)) {
  549. $share->setLabel($label);
  550. }
  551. if ($sendPasswordByTalk === 'true') {
  552. if (!$this->appManager->isEnabledForUser('spreed')) {
  553. throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
  554. }
  555. $share->setSendPasswordByTalk(true);
  556. }
  557. //Expire date
  558. if ($expireDate !== '') {
  559. try {
  560. $expireDate = $this->parseDate($expireDate);
  561. $share->setExpirationDate($expireDate);
  562. } catch (\Exception $e) {
  563. throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
  564. }
  565. }
  566. } elseif ($shareType === IShare::TYPE_REMOTE) {
  567. if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
  568. throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
  569. }
  570. if ($shareWith === null) {
  571. throw new OCSNotFoundException($this->l->t('Please specify a valid federated user ID'));
  572. }
  573. $share->setSharedWith($shareWith);
  574. $share->setPermissions($permissions);
  575. if ($expireDate !== '') {
  576. try {
  577. $expireDate = $this->parseDate($expireDate);
  578. $share->setExpirationDate($expireDate);
  579. } catch (\Exception $e) {
  580. throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
  581. }
  582. }
  583. } elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
  584. if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
  585. throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
  586. }
  587. if ($shareWith === null) {
  588. throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
  589. }
  590. $share->setSharedWith($shareWith);
  591. $share->setPermissions($permissions);
  592. if ($expireDate !== '') {
  593. try {
  594. $expireDate = $this->parseDate($expireDate);
  595. $share->setExpirationDate($expireDate);
  596. } catch (\Exception $e) {
  597. throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD'));
  598. }
  599. }
  600. } elseif ($shareType === IShare::TYPE_CIRCLE) {
  601. if (!\OC::$server->getAppManager()->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
  602. throw new OCSNotFoundException($this->l->t('You cannot share to a Circle if the app is not enabled'));
  603. }
  604. $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($shareWith);
  605. // Valid circle is required to share
  606. if ($circle === null) {
  607. throw new OCSNotFoundException($this->l->t('Please specify a valid circle'));
  608. }
  609. $share->setSharedWith($shareWith);
  610. $share->setPermissions($permissions);
  611. } elseif ($shareType === IShare::TYPE_ROOM) {
  612. try {
  613. $this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
  614. } catch (QueryException $e) {
  615. throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
  616. }
  617. } elseif ($shareType === IShare::TYPE_DECK) {
  618. try {
  619. $this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
  620. } catch (QueryException $e) {
  621. throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
  622. }
  623. } elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
  624. try {
  625. $this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate);
  626. } catch (QueryException $e) {
  627. throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support sciencemesh shares', [$node->getPath()]));
  628. }
  629. } else {
  630. throw new OCSBadRequestException($this->l->t('Unknown share type'));
  631. }
  632. $share->setShareType($shareType);
  633. if ($note !== '') {
  634. $share->setNote($note);
  635. }
  636. try {
  637. $share = $this->shareManager->createShare($share);
  638. } catch (GenericShareException $e) {
  639. \OC::$server->getLogger()->logException($e);
  640. $code = $e->getCode() === 0 ? 403 : $e->getCode();
  641. throw new OCSException($e->getHint(), $code);
  642. } catch (\Exception $e) {
  643. \OC::$server->getLogger()->logException($e);
  644. throw new OCSForbiddenException($e->getMessage(), $e);
  645. }
  646. $output = $this->formatShare($share);
  647. return new DataResponse($output);
  648. }
  649. /**
  650. * @param null|Node $node
  651. * @param boolean $includeTags
  652. *
  653. * @return array
  654. */
  655. private function getSharedWithMe($node, bool $includeTags): array {
  656. $userShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_USER, $node, -1, 0);
  657. $groupShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_GROUP, $node, -1, 0);
  658. $circleShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_CIRCLE, $node, -1, 0);
  659. $roomShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_ROOM, $node, -1, 0);
  660. $deckShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_DECK, $node, -1, 0);
  661. $sciencemeshShares = $this->shareManager->getSharedWith($this->currentUser, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
  662. $shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
  663. $filteredShares = array_filter($shares, function (IShare $share) {
  664. return $share->getShareOwner() !== $this->currentUser;
  665. });
  666. $formatted = [];
  667. foreach ($filteredShares as $share) {
  668. if ($this->canAccessShare($share)) {
  669. try {
  670. $formatted[] = $this->formatShare($share);
  671. } catch (NotFoundException $e) {
  672. // Ignore this share
  673. }
  674. }
  675. }
  676. if ($includeTags) {
  677. $formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
  678. }
  679. return $formatted;
  680. }
  681. /**
  682. * @param \OCP\Files\Node $folder
  683. *
  684. * @return array
  685. * @throws OCSBadRequestException
  686. * @throws NotFoundException
  687. */
  688. private function getSharesInDir(Node $folder): array {
  689. if (!($folder instanceof \OCP\Files\Folder)) {
  690. throw new OCSBadRequestException($this->l->t('Not a directory'));
  691. }
  692. $nodes = $folder->getDirectoryListing();
  693. /** @var \OCP\Share\IShare[] $shares */
  694. $shares = array_reduce($nodes, function ($carry, $node) {
  695. $carry = array_merge($carry, $this->getAllShares($node, true));
  696. return $carry;
  697. }, []);
  698. // filter out duplicate shares
  699. $known = [];
  700. $formatted = $miniFormatted = [];
  701. $resharingRight = false;
  702. $known = [];
  703. foreach ($shares as $share) {
  704. if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->currentUser) {
  705. continue;
  706. }
  707. try {
  708. $format = $this->formatShare($share);
  709. $known[] = $share->getId();
  710. $formatted[] = $format;
  711. if ($share->getSharedBy() === $this->currentUser) {
  712. $miniFormatted[] = $format;
  713. }
  714. if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $folder)) {
  715. $resharingRight = true;
  716. }
  717. } catch (\Exception $e) {
  718. //Ignore this share
  719. }
  720. }
  721. if (!$resharingRight) {
  722. $formatted = $miniFormatted;
  723. }
  724. return $formatted;
  725. }
  726. /**
  727. * The getShares function.
  728. *
  729. * @NoAdminRequired
  730. *
  731. * @param string $shared_with_me
  732. * @param string $reshares
  733. * @param string $subfiles
  734. * @param string $path
  735. *
  736. * - Get shares by the current user
  737. * - Get shares by the current user and reshares (?reshares=true)
  738. * - Get shares with the current user (?shared_with_me=true)
  739. * - Get shares for a specific path (?path=...)
  740. * - Get all shares in a folder (?subfiles=true&path=..)
  741. *
  742. * @param string $include_tags
  743. *
  744. * @return DataResponse
  745. * @throws NotFoundException
  746. * @throws OCSBadRequestException
  747. * @throws OCSNotFoundException
  748. */
  749. public function getShares(
  750. string $shared_with_me = 'false',
  751. string $reshares = 'false',
  752. string $subfiles = 'false',
  753. string $path = '',
  754. string $include_tags = 'false'
  755. ): DataResponse {
  756. $node = null;
  757. if ($path !== '') {
  758. $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
  759. try {
  760. $node = $userFolder->get($path);
  761. $this->lock($node);
  762. } catch (NotFoundException $e) {
  763. throw new OCSNotFoundException(
  764. $this->l->t('Wrong path, file/folder does not exist')
  765. );
  766. } catch (LockedException $e) {
  767. throw new OCSNotFoundException($this->l->t('Could not lock node'));
  768. }
  769. }
  770. $shares = $this->getFormattedShares(
  771. $this->currentUser,
  772. $node,
  773. ($shared_with_me === 'true'),
  774. ($reshares === 'true'),
  775. ($subfiles === 'true'),
  776. ($include_tags === 'true')
  777. );
  778. return new DataResponse($shares);
  779. }
  780. /**
  781. * @param string $viewer
  782. * @param Node $node
  783. * @param bool $sharedWithMe
  784. * @param bool $reShares
  785. * @param bool $subFiles
  786. * @param bool $includeTags
  787. *
  788. * @return array
  789. * @throws NotFoundException
  790. * @throws OCSBadRequestException
  791. */
  792. private function getFormattedShares(
  793. string $viewer,
  794. $node = null,
  795. bool $sharedWithMe = false,
  796. bool $reShares = false,
  797. bool $subFiles = false,
  798. bool $includeTags = false
  799. ): array {
  800. if ($sharedWithMe) {
  801. return $this->getSharedWithMe($node, $includeTags);
  802. }
  803. if ($subFiles) {
  804. return $this->getSharesInDir($node);
  805. }
  806. $shares = $this->getSharesFromNode($viewer, $node, $reShares);
  807. $known = $formatted = $miniFormatted = [];
  808. $resharingRight = false;
  809. foreach ($shares as $share) {
  810. try {
  811. $share->getNode();
  812. } catch (NotFoundException $e) {
  813. /*
  814. * Ignore shares where we can't get the node
  815. * For example deleted shares
  816. */
  817. continue;
  818. }
  819. if (in_array($share->getId(), $known)
  820. || ($share->getSharedWith() === $this->currentUser && $share->getShareType() === IShare::TYPE_USER)) {
  821. continue;
  822. }
  823. $known[] = $share->getId();
  824. try {
  825. /** @var IShare $share */
  826. $format = $this->formatShare($share, $node);
  827. $formatted[] = $format;
  828. // let's also build a list of shares created
  829. // by the current user only, in case
  830. // there is no resharing rights
  831. if ($share->getSharedBy() === $this->currentUser) {
  832. $miniFormatted[] = $format;
  833. }
  834. // check if one of those share is shared with me
  835. // and if I have resharing rights on it
  836. if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $node)) {
  837. $resharingRight = true;
  838. }
  839. } catch (InvalidPathException | NotFoundException $e) {
  840. }
  841. }
  842. if (!$resharingRight) {
  843. $formatted = $miniFormatted;
  844. }
  845. if ($includeTags) {
  846. $formatted =
  847. Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager());
  848. }
  849. return $formatted;
  850. }
  851. /**
  852. * The getInheritedShares function.
  853. * returns all shares relative to a file, including parent folders shares rights.
  854. *
  855. * @NoAdminRequired
  856. *
  857. * @param string $path
  858. *
  859. * - Get shares by the current user
  860. * - Get shares by the current user and reshares (?reshares=true)
  861. * - Get shares with the current user (?shared_with_me=true)
  862. * - Get shares for a specific path (?path=...)
  863. * - Get all shares in a folder (?subfiles=true&path=..)
  864. *
  865. * @return DataResponse
  866. * @throws InvalidPathException
  867. * @throws NotFoundException
  868. * @throws OCSNotFoundException
  869. * @throws OCSBadRequestException
  870. * @throws SharingRightsException
  871. */
  872. public function getInheritedShares(string $path): DataResponse {
  873. // get Node from (string) path.
  874. $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
  875. try {
  876. $node = $userFolder->get($path);
  877. $this->lock($node);
  878. } catch (\OCP\Files\NotFoundException $e) {
  879. throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
  880. } catch (LockedException $e) {
  881. throw new OCSNotFoundException($this->l->t('Could not lock path'));
  882. }
  883. if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
  884. throw new SharingRightsException('no sharing rights on this item');
  885. }
  886. // The current top parent we have access to
  887. $parent = $node;
  888. // initiate real owner.
  889. $owner = $node->getOwner()
  890. ->getUID();
  891. if (!$this->userManager->userExists($owner)) {
  892. return new DataResponse([]);
  893. }
  894. // get node based on the owner, fix owner in case of external storage
  895. $userFolder = $this->rootFolder->getUserFolder($owner);
  896. if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
  897. $owner = $node->getOwner()
  898. ->getUID();
  899. $userFolder = $this->rootFolder->getUserFolder($owner);
  900. $nodes = $userFolder->getById($node->getId());
  901. $node = array_shift($nodes);
  902. }
  903. $basePath = $userFolder->getPath();
  904. // generate node list for each parent folders
  905. /** @var Node[] $nodes */
  906. $nodes = [];
  907. while ($node->getPath() !== $basePath) {
  908. $node = $node->getParent();
  909. $nodes[] = $node;
  910. }
  911. // The user that is requesting this list
  912. $currentUserFolder = $this->rootFolder->getUserFolder($this->currentUser);
  913. // for each nodes, retrieve shares.
  914. $shares = [];
  915. foreach ($nodes as $node) {
  916. $getShares = $this->getFormattedShares($owner, $node, false, true);
  917. $currentUserNodes = $currentUserFolder->getById($node->getId());
  918. if (!empty($currentUserNodes)) {
  919. $parent = array_pop($currentUserNodes);
  920. }
  921. $subPath = $currentUserFolder->getRelativePath($parent->getPath());
  922. foreach ($getShares as &$share) {
  923. $share['via_fileid'] = $parent->getId();
  924. $share['via_path'] = $subPath;
  925. }
  926. $this->mergeFormattedShares($shares, $getShares);
  927. }
  928. return new DataResponse(array_values($shares));
  929. }
  930. /**
  931. * Check whether a set of permissions contains the permissions to check.
  932. */
  933. private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
  934. return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
  935. }
  936. /**
  937. * @NoAdminRequired
  938. *
  939. * @param string $id
  940. * @param int $permissions
  941. * @param string $password
  942. * @param string $sendPasswordByTalk
  943. * @param string $publicUpload
  944. * @param string $expireDate
  945. * @param string $note
  946. * @param string $label
  947. * @param string $hideDownload
  948. * @param string $attributes
  949. * @return DataResponse
  950. * @throws LockedException
  951. * @throws NotFoundException
  952. * @throws OCSBadRequestException
  953. * @throws OCSForbiddenException
  954. * @throws OCSNotFoundException
  955. */
  956. public function updateShare(
  957. string $id,
  958. int $permissions = null,
  959. string $password = null,
  960. string $sendPasswordByTalk = null,
  961. string $publicUpload = null,
  962. string $expireDate = null,
  963. string $note = null,
  964. string $label = null,
  965. string $hideDownload = null,
  966. string $attributes = null
  967. ): DataResponse {
  968. try {
  969. $share = $this->getShareById($id);
  970. } catch (ShareNotFound $e) {
  971. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  972. }
  973. $this->lock($share->getNode());
  974. if (!$this->canAccessShare($share, false)) {
  975. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  976. }
  977. if (!$this->canEditShare($share)) {
  978. throw new OCSForbiddenException('You are not allowed to edit incoming shares');
  979. }
  980. if (
  981. $permissions === null &&
  982. $password === null &&
  983. $sendPasswordByTalk === null &&
  984. $publicUpload === null &&
  985. $expireDate === null &&
  986. $note === null &&
  987. $label === null &&
  988. $hideDownload === null &&
  989. $attributes === null
  990. ) {
  991. throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
  992. }
  993. if ($note !== null) {
  994. $share->setNote($note);
  995. }
  996. if ($attributes !== null) {
  997. $share = $this->setShareAttributes($share, $attributes);
  998. }
  999. $this->checkInheritedAttributes($share);
  1000. /**
  1001. * expirationdate, password and publicUpload only make sense for link shares
  1002. */
  1003. if ($share->getShareType() === IShare::TYPE_LINK
  1004. || $share->getShareType() === IShare::TYPE_EMAIL) {
  1005. /**
  1006. * We do not allow editing link shares that the current user
  1007. * doesn't own. This is confusing and lead to errors when
  1008. * someone else edit a password or expiration date without
  1009. * the share owner knowing about it.
  1010. * We only allow deletion
  1011. */
  1012. if ($share->getSharedBy() !== $this->currentUser) {
  1013. throw new OCSForbiddenException('You are not allowed to edit link shares that you don\'t own');
  1014. }
  1015. // Update hide download state
  1016. if ($hideDownload === 'true') {
  1017. $share->setHideDownload(true);
  1018. } elseif ($hideDownload === 'false') {
  1019. $share->setHideDownload(false);
  1020. }
  1021. $newPermissions = null;
  1022. if ($publicUpload === 'true') {
  1023. $newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
  1024. } elseif ($publicUpload === 'false') {
  1025. $newPermissions = Constants::PERMISSION_READ;
  1026. }
  1027. if ($permissions !== null) {
  1028. $newPermissions = $permissions;
  1029. $newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
  1030. }
  1031. if ($newPermissions !== null) {
  1032. if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && !$this->hasPermission($newPermissions, Constants::PERMISSION_CREATE)) {
  1033. throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
  1034. }
  1035. if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && (
  1036. $this->hasPermission($newPermissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($newPermissions, Constants::PERMISSION_DELETE)
  1037. )) {
  1038. throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
  1039. }
  1040. }
  1041. if (
  1042. // legacy
  1043. $newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
  1044. // correct
  1045. $newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
  1046. ) {
  1047. if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
  1048. throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
  1049. }
  1050. if (!($share->getNode() instanceof \OCP\Files\Folder)) {
  1051. throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
  1052. }
  1053. // normalize to correct public upload permissions
  1054. if ($publicUpload === 'true') {
  1055. $newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
  1056. }
  1057. }
  1058. if ($newPermissions !== null) {
  1059. // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
  1060. if (($newPermissions & Constants::PERMISSION_READ) && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
  1061. $newPermissions |= Constants::PERMISSION_SHARE;
  1062. }
  1063. $share->setPermissions($newPermissions);
  1064. $permissions = $newPermissions;
  1065. }
  1066. if ($expireDate === '') {
  1067. $share->setExpirationDate(null);
  1068. } elseif ($expireDate !== null) {
  1069. try {
  1070. $expireDate = $this->parseDate($expireDate);
  1071. } catch (\Exception $e) {
  1072. throw new OCSBadRequestException($e->getMessage(), $e);
  1073. }
  1074. $share->setExpirationDate($expireDate);
  1075. }
  1076. if ($password === '') {
  1077. $share->setPassword(null);
  1078. } elseif ($password !== null) {
  1079. $share->setPassword($password);
  1080. }
  1081. if ($label !== null) {
  1082. if (strlen($label) > 255) {
  1083. throw new OCSBadRequestException("Maximum label length is 255");
  1084. }
  1085. $share->setLabel($label);
  1086. }
  1087. if ($sendPasswordByTalk === 'true') {
  1088. if (!$this->appManager->isEnabledForUser('spreed')) {
  1089. throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
  1090. }
  1091. $share->setSendPasswordByTalk(true);
  1092. } elseif ($sendPasswordByTalk !== null) {
  1093. $share->setSendPasswordByTalk(false);
  1094. }
  1095. }
  1096. // NOT A LINK SHARE
  1097. else {
  1098. if ($permissions !== null) {
  1099. $share->setPermissions($permissions);
  1100. }
  1101. if ($expireDate === '') {
  1102. $share->setExpirationDate(null);
  1103. } elseif ($expireDate !== null) {
  1104. try {
  1105. $expireDate = $this->parseDate($expireDate);
  1106. } catch (\Exception $e) {
  1107. throw new OCSBadRequestException($e->getMessage(), $e);
  1108. }
  1109. $share->setExpirationDate($expireDate);
  1110. }
  1111. }
  1112. try {
  1113. $share = $this->shareManager->updateShare($share);
  1114. } catch (GenericShareException $e) {
  1115. $code = $e->getCode() === 0 ? 403 : $e->getCode();
  1116. throw new OCSException($e->getHint(), $code);
  1117. } catch (\Exception $e) {
  1118. throw new OCSBadRequestException($e->getMessage(), $e);
  1119. }
  1120. return new DataResponse($this->formatShare($share));
  1121. }
  1122. /**
  1123. * @NoAdminRequired
  1124. */
  1125. public function pendingShares(): DataResponse {
  1126. $pendingShares = [];
  1127. $shareTypes = [
  1128. IShare::TYPE_USER,
  1129. IShare::TYPE_GROUP
  1130. ];
  1131. foreach ($shareTypes as $shareType) {
  1132. $shares = $this->shareManager->getSharedWith($this->currentUser, $shareType, null, -1, 0);
  1133. foreach ($shares as $share) {
  1134. if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
  1135. $pendingShares[] = $share;
  1136. }
  1137. }
  1138. }
  1139. $result = array_filter(array_map(function (IShare $share) {
  1140. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  1141. $nodes = $userFolder->getById($share->getNodeId());
  1142. if (empty($nodes)) {
  1143. // fallback to guessing the path
  1144. $node = $userFolder->get($share->getTarget());
  1145. if ($node === null || $share->getTarget() === '') {
  1146. return null;
  1147. }
  1148. } else {
  1149. $node = $nodes[0];
  1150. }
  1151. try {
  1152. $formattedShare = $this->formatShare($share, $node);
  1153. $formattedShare['status'] = $share->getStatus();
  1154. $formattedShare['path'] = $share->getNode()->getName();
  1155. $formattedShare['permissions'] = 0;
  1156. return $formattedShare;
  1157. } catch (NotFoundException $e) {
  1158. return null;
  1159. }
  1160. }, $pendingShares), function ($entry) {
  1161. return $entry !== null;
  1162. });
  1163. return new DataResponse($result);
  1164. }
  1165. /**
  1166. * @NoAdminRequired
  1167. *
  1168. * @param string $id
  1169. * @return DataResponse
  1170. * @throws OCSNotFoundException
  1171. * @throws OCSException
  1172. * @throws OCSBadRequestException
  1173. */
  1174. public function acceptShare(string $id): DataResponse {
  1175. try {
  1176. $share = $this->getShareById($id);
  1177. } catch (ShareNotFound $e) {
  1178. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  1179. }
  1180. if (!$this->canAccessShare($share)) {
  1181. throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
  1182. }
  1183. try {
  1184. $this->shareManager->acceptShare($share, $this->currentUser);
  1185. } catch (GenericShareException $e) {
  1186. $code = $e->getCode() === 0 ? 403 : $e->getCode();
  1187. throw new OCSException($e->getHint(), $code);
  1188. } catch (\Exception $e) {
  1189. throw new OCSBadRequestException($e->getMessage(), $e);
  1190. }
  1191. return new DataResponse();
  1192. }
  1193. /**
  1194. * Does the user have read permission on the share
  1195. *
  1196. * @param \OCP\Share\IShare $share the share to check
  1197. * @param boolean $checkGroups check groups as well?
  1198. * @return boolean
  1199. * @throws NotFoundException
  1200. *
  1201. * @suppress PhanUndeclaredClassMethod
  1202. */
  1203. protected function canAccessShare(\OCP\Share\IShare $share, bool $checkGroups = true): bool {
  1204. // A file with permissions 0 can't be accessed by us. So Don't show it
  1205. if ($share->getPermissions() === 0) {
  1206. return false;
  1207. }
  1208. // Owner of the file and the sharer of the file can always get share
  1209. if ($share->getShareOwner() === $this->currentUser
  1210. || $share->getSharedBy() === $this->currentUser) {
  1211. return true;
  1212. }
  1213. // If the share is shared with you, you can access it!
  1214. if ($share->getShareType() === IShare::TYPE_USER
  1215. && $share->getSharedWith() === $this->currentUser) {
  1216. return true;
  1217. }
  1218. // Have reshare rights on the shared file/folder ?
  1219. // Does the currentUser have access to the shared file?
  1220. $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
  1221. $files = $userFolder->getById($share->getNodeId());
  1222. if (!empty($files) && $this->shareProviderResharingRights($this->currentUser, $share, $files[0])) {
  1223. return true;
  1224. }
  1225. // If in the recipient group, you can see the share
  1226. if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
  1227. $sharedWith = $this->groupManager->get($share->getSharedWith());
  1228. $user = $this->userManager->get($this->currentUser);
  1229. if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
  1230. return true;
  1231. }
  1232. }
  1233. if ($share->getShareType() === IShare::TYPE_CIRCLE) {
  1234. // TODO: have a sanity check like above?
  1235. return true;
  1236. }
  1237. if ($share->getShareType() === IShare::TYPE_ROOM) {
  1238. try {
  1239. return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
  1240. } catch (QueryException $e) {
  1241. return false;
  1242. }
  1243. }
  1244. if ($share->getShareType() === IShare::TYPE_DECK) {
  1245. try {
  1246. return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
  1247. } catch (QueryException $e) {
  1248. return false;
  1249. }
  1250. }
  1251. if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
  1252. try {
  1253. return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
  1254. } catch (QueryException $e) {
  1255. return false;
  1256. }
  1257. }
  1258. return false;
  1259. }
  1260. /**
  1261. * Does the user have edit permission on the share
  1262. *
  1263. * @param \OCP\Share\IShare $share the share to check
  1264. * @return boolean
  1265. */
  1266. protected function canEditShare(\OCP\Share\IShare $share): bool {
  1267. // A file with permissions 0 can't be accessed by us. So Don't show it
  1268. if ($share->getPermissions() === 0) {
  1269. return false;
  1270. }
  1271. // The owner of the file and the creator of the share
  1272. // can always edit the share
  1273. if ($share->getShareOwner() === $this->currentUser ||
  1274. $share->getSharedBy() === $this->currentUser
  1275. ) {
  1276. return true;
  1277. }
  1278. //! we do NOT support some kind of `admin` in groups.
  1279. //! You cannot edit shares shared to a group you're
  1280. //! a member of if you're not the share owner or the file owner!
  1281. return false;
  1282. }
  1283. /**
  1284. * Does the user have delete permission on the share
  1285. *
  1286. * @param \OCP\Share\IShare $share the share to check
  1287. * @return boolean
  1288. */
  1289. protected function canDeleteShare(\OCP\Share\IShare $share): bool {
  1290. // A file with permissions 0 can't be accessed by us. So Don't show it
  1291. if ($share->getPermissions() === 0) {
  1292. return false;
  1293. }
  1294. // if the user is the recipient, i can unshare
  1295. // the share with self
  1296. if ($share->getShareType() === IShare::TYPE_USER &&
  1297. $share->getSharedWith() === $this->currentUser
  1298. ) {
  1299. return true;
  1300. }
  1301. // The owner of the file and the creator of the share
  1302. // can always delete the share
  1303. if ($share->getShareOwner() === $this->currentUser ||
  1304. $share->getSharedBy() === $this->currentUser
  1305. ) {
  1306. return true;
  1307. }
  1308. return false;
  1309. }
  1310. /**
  1311. * Does the user have delete permission on the share
  1312. * This differs from the canDeleteShare function as it only
  1313. * remove the share for the current user. It does NOT
  1314. * completely delete the share but only the mount point.
  1315. * It can then be restored from the deleted shares section.
  1316. *
  1317. * @param \OCP\Share\IShare $share the share to check
  1318. * @return boolean
  1319. *
  1320. * @suppress PhanUndeclaredClassMethod
  1321. */
  1322. protected function canDeleteShareFromSelf(\OCP\Share\IShare $share): bool {
  1323. if ($share->getShareType() !== IShare::TYPE_GROUP &&
  1324. $share->getShareType() !== IShare::TYPE_ROOM &&
  1325. $share->getShareType() !== IShare::TYPE_DECK &&
  1326. $share->getShareType() !== IShare::TYPE_SCIENCEMESH
  1327. ) {
  1328. return false;
  1329. }
  1330. if ($share->getShareOwner() === $this->currentUser ||
  1331. $share->getSharedBy() === $this->currentUser
  1332. ) {
  1333. // Delete the whole share, not just for self
  1334. return false;
  1335. }
  1336. // If in the recipient group, you can delete the share from self
  1337. if ($share->getShareType() === IShare::TYPE_GROUP) {
  1338. $sharedWith = $this->groupManager->get($share->getSharedWith());
  1339. $user = $this->userManager->get($this->currentUser);
  1340. if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
  1341. return true;
  1342. }
  1343. }
  1344. if ($share->getShareType() === IShare::TYPE_ROOM) {
  1345. try {
  1346. return $this->getRoomShareHelper()->canAccessShare($share, $this->currentUser);
  1347. } catch (QueryException $e) {
  1348. return false;
  1349. }
  1350. }
  1351. if ($share->getShareType() === IShare::TYPE_DECK) {
  1352. try {
  1353. return $this->getDeckShareHelper()->canAccessShare($share, $this->currentUser);
  1354. } catch (QueryException $e) {
  1355. return false;
  1356. }
  1357. }
  1358. if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
  1359. try {
  1360. return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->currentUser);
  1361. } catch (QueryException $e) {
  1362. return false;
  1363. }
  1364. }
  1365. return false;
  1366. }
  1367. /**
  1368. * Make sure that the passed date is valid ISO 8601
  1369. * So YYYY-MM-DD
  1370. * If not throw an exception
  1371. *
  1372. * @param string $expireDate
  1373. *
  1374. * @throws \Exception
  1375. * @return \DateTime
  1376. */
  1377. private function parseDate(string $expireDate): \DateTime {
  1378. try {
  1379. $date = new \DateTime(trim($expireDate, "\""));
  1380. } catch (\Exception $e) {
  1381. throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
  1382. }
  1383. $date->setTime(0, 0, 0);
  1384. return $date;
  1385. }
  1386. /**
  1387. * Since we have multiple providers but the OCS Share API v1 does
  1388. * not support this we need to check all backends.
  1389. *
  1390. * @param string $id
  1391. * @return \OCP\Share\IShare
  1392. * @throws ShareNotFound
  1393. */
  1394. private function getShareById(string $id): IShare {
  1395. $share = null;
  1396. // First check if it is an internal share.
  1397. try {
  1398. $share = $this->shareManager->getShareById('ocinternal:' . $id, $this->currentUser);
  1399. return $share;
  1400. } catch (ShareNotFound $e) {
  1401. // Do nothing, just try the other share type
  1402. }
  1403. try {
  1404. if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
  1405. $share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->currentUser);
  1406. return $share;
  1407. }
  1408. } catch (ShareNotFound $e) {
  1409. // Do nothing, just try the other share type
  1410. }
  1411. try {
  1412. if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
  1413. $share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->currentUser);
  1414. return $share;
  1415. }
  1416. } catch (ShareNotFound $e) {
  1417. // Do nothing, just try the other share type
  1418. }
  1419. try {
  1420. $share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->currentUser);
  1421. return $share;
  1422. } catch (ShareNotFound $e) {
  1423. // Do nothing, just try the other share type
  1424. }
  1425. try {
  1426. if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
  1427. $share = $this->shareManager->getShareById('deck:' . $id, $this->currentUser);
  1428. return $share;
  1429. }
  1430. } catch (ShareNotFound $e) {
  1431. // Do nothing, just try the other share type
  1432. }
  1433. try {
  1434. if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
  1435. $share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->currentUser);
  1436. return $share;
  1437. }
  1438. } catch (ShareNotFound $e) {
  1439. // Do nothing, just try the other share type
  1440. }
  1441. if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
  1442. throw new ShareNotFound();
  1443. }
  1444. $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->currentUser);
  1445. return $share;
  1446. }
  1447. /**
  1448. * Lock a Node
  1449. *
  1450. * @param \OCP\Files\Node $node
  1451. * @throws LockedException
  1452. */
  1453. private function lock(\OCP\Files\Node $node) {
  1454. $node->lock(ILockingProvider::LOCK_SHARED);
  1455. $this->lockedNode = $node;
  1456. }
  1457. /**
  1458. * Cleanup the remaining locks
  1459. * @throws LockedException
  1460. */
  1461. public function cleanup() {
  1462. if ($this->lockedNode !== null) {
  1463. $this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
  1464. }
  1465. }
  1466. /**
  1467. * Returns the helper of ShareAPIController for room shares.
  1468. *
  1469. * If the Talk application is not enabled or the helper is not available
  1470. * a QueryException is thrown instead.
  1471. *
  1472. * @return \OCA\Talk\Share\Helper\ShareAPIController
  1473. * @throws QueryException
  1474. */
  1475. private function getRoomShareHelper() {
  1476. if (!$this->appManager->isEnabledForUser('spreed')) {
  1477. throw new QueryException();
  1478. }
  1479. return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
  1480. }
  1481. /**
  1482. * Returns the helper of ShareAPIHelper for deck shares.
  1483. *
  1484. * If the Deck application is not enabled or the helper is not available
  1485. * a QueryException is thrown instead.
  1486. *
  1487. * @return \OCA\Deck\Sharing\ShareAPIHelper
  1488. * @throws QueryException
  1489. */
  1490. private function getDeckShareHelper() {
  1491. if (!$this->appManager->isEnabledForUser('deck')) {
  1492. throw new QueryException();
  1493. }
  1494. return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
  1495. }
  1496. /**
  1497. * Returns the helper of ShareAPIHelper for sciencemesh shares.
  1498. *
  1499. * If the sciencemesh application is not enabled or the helper is not available
  1500. * a QueryException is thrown instead.
  1501. *
  1502. * @return \OCA\Deck\Sharing\ShareAPIHelper
  1503. * @throws QueryException
  1504. */
  1505. private function getSciencemeshShareHelper() {
  1506. if (!$this->appManager->isEnabledForUser('sciencemesh')) {
  1507. throw new QueryException();
  1508. }
  1509. return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
  1510. }
  1511. /**
  1512. * @param string $viewer
  1513. * @param Node $node
  1514. * @param bool $reShares
  1515. *
  1516. * @return IShare[]
  1517. */
  1518. private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
  1519. $providers = [
  1520. IShare::TYPE_USER,
  1521. IShare::TYPE_GROUP,
  1522. IShare::TYPE_LINK,
  1523. IShare::TYPE_EMAIL,
  1524. IShare::TYPE_CIRCLE,
  1525. IShare::TYPE_ROOM,
  1526. IShare::TYPE_DECK,
  1527. IShare::TYPE_SCIENCEMESH
  1528. ];
  1529. // Should we assume that the (currentUser) viewer is the owner of the node !?
  1530. $shares = [];
  1531. foreach ($providers as $provider) {
  1532. if (!$this->shareManager->shareProviderExists($provider)) {
  1533. continue;
  1534. }
  1535. $providerShares =
  1536. $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
  1537. $shares = array_merge($shares, $providerShares);
  1538. }
  1539. if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
  1540. $federatedShares = $this->shareManager->getSharesBy(
  1541. $this->currentUser, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
  1542. );
  1543. $shares = array_merge($shares, $federatedShares);
  1544. }
  1545. if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
  1546. $federatedShares = $this->shareManager->getSharesBy(
  1547. $this->currentUser, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
  1548. );
  1549. $shares = array_merge($shares, $federatedShares);
  1550. }
  1551. return $shares;
  1552. }
  1553. /**
  1554. * @param Node $node
  1555. *
  1556. * @throws SharingRightsException
  1557. */
  1558. private function confirmSharingRights(Node $node): void {
  1559. if (!$this->hasResharingRights($this->currentUser, $node)) {
  1560. throw new SharingRightsException('no sharing rights on this item');
  1561. }
  1562. }
  1563. /**
  1564. * @param string $viewer
  1565. * @param Node $node
  1566. *
  1567. * @return bool
  1568. */
  1569. private function hasResharingRights($viewer, $node): bool {
  1570. if ($viewer === $node->getOwner()->getUID()) {
  1571. return true;
  1572. }
  1573. foreach ([$node, $node->getParent()] as $node) {
  1574. $shares = $this->getSharesFromNode($viewer, $node, true);
  1575. foreach ($shares as $share) {
  1576. try {
  1577. if ($this->shareProviderResharingRights($viewer, $share, $node)) {
  1578. return true;
  1579. }
  1580. } catch (InvalidPathException | NotFoundException $e) {
  1581. }
  1582. }
  1583. }
  1584. return false;
  1585. }
  1586. /**
  1587. * Returns if we can find resharing rights in an IShare object for a specific user.
  1588. *
  1589. * @suppress PhanUndeclaredClassMethod
  1590. *
  1591. * @param string $userId
  1592. * @param IShare $share
  1593. * @param Node $node
  1594. *
  1595. * @return bool
  1596. * @throws NotFoundException
  1597. * @throws InvalidPathException
  1598. */
  1599. private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
  1600. if ($share->getShareOwner() === $userId) {
  1601. return true;
  1602. }
  1603. // we check that current user have parent resharing rights on the current file
  1604. if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
  1605. return true;
  1606. }
  1607. if ((\OCP\Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
  1608. return false;
  1609. }
  1610. if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
  1611. return true;
  1612. }
  1613. if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
  1614. return true;
  1615. }
  1616. if ($share->getShareType() === IShare::TYPE_CIRCLE && \OC::$server->getAppManager()->isEnabledForUser('circles')
  1617. && class_exists('\OCA\Circles\Api\v1\Circles')) {
  1618. $hasCircleId = (substr($share->getSharedWith(), -1) === ']');
  1619. $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
  1620. $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
  1621. if ($shareWithLength === false) {
  1622. $sharedWith = substr($share->getSharedWith(), $shareWithStart);
  1623. } else {
  1624. $sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
  1625. }
  1626. try {
  1627. $member = \OCA\Circles\Api\v1\Circles::getMember($sharedWith, $userId, 1);
  1628. if ($member->getLevel() >= 4) {
  1629. return true;
  1630. }
  1631. return false;
  1632. } catch (QueryException $e) {
  1633. return false;
  1634. }
  1635. }
  1636. return false;
  1637. }
  1638. /**
  1639. * Get all the shares for the current user
  1640. *
  1641. * @param Node|null $path
  1642. * @param boolean $reshares
  1643. * @return IShare[]
  1644. */
  1645. private function getAllShares(?Node $path = null, bool $reshares = false) {
  1646. // Get all shares
  1647. $userShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_USER, $path, $reshares, -1, 0);
  1648. $groupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
  1649. $linkShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_LINK, $path, $reshares, -1, 0);
  1650. // EMAIL SHARES
  1651. $mailShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
  1652. // CIRCLE SHARES
  1653. $circleShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
  1654. // TALK SHARES
  1655. $roomShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
  1656. // DECK SHARES
  1657. $deckShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_DECK, $path, $reshares, -1, 0);
  1658. // SCIENCEMESH SHARES
  1659. $sciencemeshShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
  1660. // FEDERATION
  1661. if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
  1662. $federatedShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
  1663. } else {
  1664. $federatedShares = [];
  1665. }
  1666. if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
  1667. $federatedGroupShares = $this->shareManager->getSharesBy($this->currentUser, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
  1668. } else {
  1669. $federatedGroupShares = [];
  1670. }
  1671. return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
  1672. }
  1673. /**
  1674. * merging already formatted shares.
  1675. * We'll make an associative array to easily detect duplicate Ids.
  1676. * Keys _needs_ to be removed after all shares are retrieved and merged.
  1677. *
  1678. * @param array $shares
  1679. * @param array $newShares
  1680. */
  1681. private function mergeFormattedShares(array &$shares, array $newShares) {
  1682. foreach ($newShares as $newShare) {
  1683. if (!array_key_exists($newShare['id'], $shares)) {
  1684. $shares[$newShare['id']] = $newShare;
  1685. }
  1686. }
  1687. }
  1688. /**
  1689. * @param IShare $share
  1690. * @param string|null $attributesString
  1691. * @return IShare modified share
  1692. */
  1693. private function setShareAttributes(IShare $share, ?string $attributesString) {
  1694. $newShareAttributes = null;
  1695. if ($attributesString !== null) {
  1696. $newShareAttributes = $this->shareManager->newShare()->newAttributes();
  1697. $formattedShareAttributes = \json_decode($attributesString, true);
  1698. if (is_array($formattedShareAttributes)) {
  1699. foreach ($formattedShareAttributes as $formattedAttr) {
  1700. $newShareAttributes->setAttribute(
  1701. $formattedAttr['scope'],
  1702. $formattedAttr['key'],
  1703. is_string($formattedAttr['enabled']) ? (bool) \json_decode($formattedAttr['enabled']) : $formattedAttr['enabled']
  1704. );
  1705. }
  1706. } else {
  1707. throw new OCSBadRequestException('Invalid share attributes provided: \"' . $attributesString . '\"');
  1708. }
  1709. }
  1710. $share->setAttributes($newShareAttributes);
  1711. return $share;
  1712. }
  1713. private function checkInheritedAttributes(IShare $share): void {
  1714. if (!$share->getSharedBy()) {
  1715. return; // Probably in a test
  1716. }
  1717. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  1718. $nodes = $userFolder->getById($share->getNodeId());
  1719. if (empty($nodes)) {
  1720. return;
  1721. }
  1722. $node = $nodes[0];
  1723. if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
  1724. $storage = $node->getStorage();
  1725. if ($storage instanceof Wrapper) {
  1726. $storage = $storage->getInstanceOfStorage(SharedStorage::class);
  1727. if ($storage === null) {
  1728. throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
  1729. }
  1730. } else {
  1731. throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
  1732. }
  1733. /** @var \OCA\Files_Sharing\SharedStorage $storage */
  1734. $inheritedAttributes = $storage->getShare()->getAttributes();
  1735. if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
  1736. $share->setHideDownload(true);
  1737. $attributes = $share->getAttributes();
  1738. if ($attributes) {
  1739. $attributes->setAttribute('permissions', 'download', false);
  1740. $share->setAttributes($attributes);
  1741. }
  1742. }
  1743. }
  1744. }
  1745. }