ShareAPIController.php 67 KB

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