You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FederatedShareProvider.php 32KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bjoern Schiessle <bjoern@schiessle.org>
  7. * @author Björn Schießle <bjoern@schiessle.org>
  8. * @author Joas Schilling <coding@schilljs.com>
  9. * @author Lukas Reschke <lukas@statuscode.ch>
  10. * @author Maxence Lange <maxence@artificial-owl.com>
  11. * @author Morris Jobke <hey@morrisjobke.de>
  12. * @author Robin Appelman <robin@icewind.nl>
  13. * @author Roeland Jago Douma <roeland@famdouma.nl>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. *
  16. * @license AGPL-3.0
  17. *
  18. * This code is free software: you can redistribute it and/or modify
  19. * it under the terms of the GNU Affero General Public License, version 3,
  20. * as published by the Free Software Foundation.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU Affero General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU Affero General Public License, version 3,
  28. * along with this program. If not, see <http://www.gnu.org/licenses/>
  29. *
  30. */
  31. namespace OCA\FederatedFileSharing;
  32. use OC\Share20\Exception\InvalidShare;
  33. use OC\Share20\Share;
  34. use OCP\DB\QueryBuilder\IQueryBuilder;
  35. use OCP\Federation\ICloudFederationProviderManager;
  36. use OCP\Federation\ICloudIdManager;
  37. use OCP\Files\Folder;
  38. use OCP\Files\IRootFolder;
  39. use OCP\Files\Node;
  40. use OCP\Files\NotFoundException;
  41. use OCP\IConfig;
  42. use OCP\IDBConnection;
  43. use OCP\IL10N;
  44. use OCP\ILogger;
  45. use OCP\IUserManager;
  46. use OCP\Share\Exceptions\GenericShareException;
  47. use OCP\Share\Exceptions\ShareNotFound;
  48. use OCP\Share\IShare;
  49. use OCP\Share\IShareProvider;
  50. /**
  51. * Class FederatedShareProvider
  52. *
  53. * @package OCA\FederatedFileSharing
  54. */
  55. class FederatedShareProvider implements IShareProvider {
  56. const SHARE_TYPE_REMOTE = 6;
  57. /** @var IDBConnection */
  58. private $dbConnection;
  59. /** @var AddressHandler */
  60. private $addressHandler;
  61. /** @var Notifications */
  62. private $notifications;
  63. /** @var TokenHandler */
  64. private $tokenHandler;
  65. /** @var IL10N */
  66. private $l;
  67. /** @var ILogger */
  68. private $logger;
  69. /** @var IRootFolder */
  70. private $rootFolder;
  71. /** @var IConfig */
  72. private $config;
  73. /** @var string */
  74. private $externalShareTable = 'share_external';
  75. /** @var IUserManager */
  76. private $userManager;
  77. /** @var ICloudIdManager */
  78. private $cloudIdManager;
  79. /** @var \OCP\GlobalScale\IConfig */
  80. private $gsConfig;
  81. /** @var ICloudFederationProviderManager */
  82. private $cloudFederationProviderManager;
  83. /** @var array list of supported share types */
  84. private $supportedShareType = [IShare::TYPE_REMOTE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_CIRCLE];
  85. /**
  86. * DefaultShareProvider constructor.
  87. *
  88. * @param IDBConnection $connection
  89. * @param AddressHandler $addressHandler
  90. * @param Notifications $notifications
  91. * @param TokenHandler $tokenHandler
  92. * @param IL10N $l10n
  93. * @param ILogger $logger
  94. * @param IRootFolder $rootFolder
  95. * @param IConfig $config
  96. * @param IUserManager $userManager
  97. * @param ICloudIdManager $cloudIdManager
  98. * @param \OCP\GlobalScale\IConfig $globalScaleConfig
  99. * @param ICloudFederationProviderManager $cloudFederationProviderManager
  100. */
  101. public function __construct(
  102. IDBConnection $connection,
  103. AddressHandler $addressHandler,
  104. Notifications $notifications,
  105. TokenHandler $tokenHandler,
  106. IL10N $l10n,
  107. ILogger $logger,
  108. IRootFolder $rootFolder,
  109. IConfig $config,
  110. IUserManager $userManager,
  111. ICloudIdManager $cloudIdManager,
  112. \OCP\GlobalScale\IConfig $globalScaleConfig,
  113. ICloudFederationProviderManager $cloudFederationProviderManager
  114. ) {
  115. $this->dbConnection = $connection;
  116. $this->addressHandler = $addressHandler;
  117. $this->notifications = $notifications;
  118. $this->tokenHandler = $tokenHandler;
  119. $this->l = $l10n;
  120. $this->logger = $logger;
  121. $this->rootFolder = $rootFolder;
  122. $this->config = $config;
  123. $this->userManager = $userManager;
  124. $this->cloudIdManager = $cloudIdManager;
  125. $this->gsConfig = $globalScaleConfig;
  126. $this->cloudFederationProviderManager = $cloudFederationProviderManager;
  127. }
  128. /**
  129. * Return the identifier of this provider.
  130. *
  131. * @return string Containing only [a-zA-Z0-9]
  132. */
  133. public function identifier() {
  134. return 'ocFederatedSharing';
  135. }
  136. /**
  137. * Share a path
  138. *
  139. * @param IShare $share
  140. * @return IShare The share object
  141. * @throws ShareNotFound
  142. * @throws \Exception
  143. */
  144. public function create(IShare $share) {
  145. $shareWith = $share->getSharedWith();
  146. $itemSource = $share->getNodeId();
  147. $itemType = $share->getNodeType();
  148. $permissions = $share->getPermissions();
  149. $sharedBy = $share->getSharedBy();
  150. $shareType = $share->getShareType();
  151. if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE_GROUP &&
  152. !$this->isOutgoingServer2serverGroupShareEnabled()
  153. ) {
  154. $message = 'It is not allowed to send federated group shares from this server.';
  155. $message_t = $this->l->t('It is not allowed to send federated group shares from this server.');
  156. $this->logger->debug($message, ['app' => 'Federated File Sharing']);
  157. throw new \Exception($message_t);
  158. }
  159. /*
  160. * Check if file is not already shared with the remote user
  161. */
  162. $alreadyShared = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
  163. $alreadySharedGroup = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);
  164. if (!empty($alreadyShared) || !empty($alreadySharedGroup)) {
  165. $message = 'Sharing %1$s failed, because this item is already shared with %2$s';
  166. $message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with %2$s', array($share->getNode()->getName(), $shareWith));
  167. $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
  168. throw new \Exception($message_t);
  169. }
  170. // don't allow federated shares if source and target server are the same
  171. $cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
  172. $currentServer = $this->addressHandler->generateRemoteURL();
  173. $currentUser = $sharedBy;
  174. if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
  175. $message = 'Not allowed to create a federated share with the same user.';
  176. $message_t = $this->l->t('Not allowed to create a federated share with the same user');
  177. $this->logger->debug($message, ['app' => 'Federated File Sharing']);
  178. throw new \Exception($message_t);
  179. }
  180. $share->setSharedWith($cloudId->getId());
  181. try {
  182. $remoteShare = $this->getShareFromExternalShareTable($share);
  183. } catch (ShareNotFound $e) {
  184. $remoteShare = null;
  185. }
  186. if ($remoteShare) {
  187. try {
  188. $ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
  189. $shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time(), $shareType);
  190. $share->setId($shareId);
  191. list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
  192. // remote share was create successfully if we get a valid token as return
  193. $send = is_string($token) && $token !== '';
  194. } catch (\Exception $e) {
  195. // fall back to old re-share behavior if the remote server
  196. // doesn't support flat re-shares (was introduced with Nextcloud 9.1)
  197. $this->removeShareFromTable($share);
  198. $shareId = $this->createFederatedShare($share);
  199. }
  200. if ($send) {
  201. $this->updateSuccessfulReshare($shareId, $token);
  202. $this->storeRemoteId($shareId, $remoteId);
  203. } else {
  204. $this->removeShareFromTable($share);
  205. $message_t = $this->l->t('File is already shared with %s', [$shareWith]);
  206. throw new \Exception($message_t);
  207. }
  208. } else {
  209. $shareId = $this->createFederatedShare($share);
  210. }
  211. $data = $this->getRawShare($shareId);
  212. return $this->createShareObject($data);
  213. }
  214. /**
  215. * create federated share and inform the recipient
  216. *
  217. * @param IShare $share
  218. * @return int
  219. * @throws ShareNotFound
  220. * @throws \Exception
  221. */
  222. protected function createFederatedShare(IShare $share) {
  223. $token = $this->tokenHandler->generateToken();
  224. $shareId = $this->addShareToDB(
  225. $share->getNodeId(),
  226. $share->getNodeType(),
  227. $share->getSharedWith(),
  228. $share->getSharedBy(),
  229. $share->getShareOwner(),
  230. $share->getPermissions(),
  231. $token,
  232. $share->getShareType()
  233. );
  234. $failure = false;
  235. try {
  236. $sharedByFederatedId = $share->getSharedBy();
  237. if ($this->userManager->userExists($sharedByFederatedId)) {
  238. $cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
  239. $sharedByFederatedId = $cloudId->getId();
  240. }
  241. $ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
  242. $send = $this->notifications->sendRemoteShare(
  243. $token,
  244. $share->getSharedWith(),
  245. $share->getNode()->getName(),
  246. $shareId,
  247. $share->getShareOwner(),
  248. $ownerCloudId->getId(),
  249. $share->getSharedBy(),
  250. $sharedByFederatedId,
  251. $share->getShareType()
  252. );
  253. if ($send === false) {
  254. $failure = true;
  255. }
  256. } catch (\Exception $e) {
  257. $this->logger->logException($e, [
  258. 'message' => 'Failed to notify remote server of federated share, removing share.',
  259. 'level' => ILogger::ERROR,
  260. 'app' => 'federatedfilesharing',
  261. ]);
  262. $failure = true;
  263. }
  264. if($failure) {
  265. $this->removeShareFromTableById($shareId);
  266. $message_t = $this->l->t('Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate.',
  267. [$share->getNode()->getName(), $share->getSharedWith()]);
  268. throw new \Exception($message_t);
  269. }
  270. return $shareId;
  271. }
  272. /**
  273. * @param string $shareWith
  274. * @param IShare $share
  275. * @param string $shareId internal share Id
  276. * @return array
  277. * @throws \Exception
  278. */
  279. protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
  280. $remoteShare = $this->getShareFromExternalShareTable($share);
  281. $token = $remoteShare['share_token'];
  282. $remoteId = $remoteShare['remote_id'];
  283. $remote = $remoteShare['remote'];
  284. list($token, $remoteId) = $this->notifications->requestReShare(
  285. $token,
  286. $remoteId,
  287. $shareId,
  288. $remote,
  289. $shareWith,
  290. $share->getPermissions(),
  291. $share->getNode()->getName()
  292. );
  293. return [$token, $remoteId];
  294. }
  295. /**
  296. * get federated share from the share_external table but exclude mounted link shares
  297. *
  298. * @param IShare $share
  299. * @return array
  300. * @throws ShareNotFound
  301. */
  302. protected function getShareFromExternalShareTable(IShare $share) {
  303. $query = $this->dbConnection->getQueryBuilder();
  304. $query->select('*')->from($this->externalShareTable)
  305. ->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
  306. ->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
  307. $result = $query->execute()->fetchAll();
  308. if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
  309. return $result[0];
  310. }
  311. throw new ShareNotFound('share not found in share_external table');
  312. }
  313. /**
  314. * add share to the database and return the ID
  315. *
  316. * @param int $itemSource
  317. * @param string $itemType
  318. * @param string $shareWith
  319. * @param string $sharedBy
  320. * @param string $uidOwner
  321. * @param int $permissions
  322. * @param string $token
  323. * @param int $shareType
  324. * @return int
  325. */
  326. private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType) {
  327. $qb = $this->dbConnection->getQueryBuilder();
  328. $qb->insert('share')
  329. ->setValue('share_type', $qb->createNamedParameter($shareType))
  330. ->setValue('item_type', $qb->createNamedParameter($itemType))
  331. ->setValue('item_source', $qb->createNamedParameter($itemSource))
  332. ->setValue('file_source', $qb->createNamedParameter($itemSource))
  333. ->setValue('share_with', $qb->createNamedParameter($shareWith))
  334. ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
  335. ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
  336. ->setValue('permissions', $qb->createNamedParameter($permissions))
  337. ->setValue('token', $qb->createNamedParameter($token))
  338. ->setValue('stime', $qb->createNamedParameter(time()));
  339. /*
  340. * Added to fix https://github.com/owncloud/core/issues/22215
  341. * Can be removed once we get rid of ajax/share.php
  342. */
  343. $qb->setValue('file_target', $qb->createNamedParameter(''));
  344. $qb->execute();
  345. $id = $qb->getLastInsertId();
  346. return (int)$id;
  347. }
  348. /**
  349. * Update a share
  350. *
  351. * @param IShare $share
  352. * @return IShare The share object
  353. */
  354. public function update(IShare $share) {
  355. /*
  356. * We allow updating the permissions of federated shares
  357. */
  358. $qb = $this->dbConnection->getQueryBuilder();
  359. $qb->update('share')
  360. ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
  361. ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
  362. ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
  363. ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
  364. ->execute();
  365. // send the updated permission to the owner/initiator, if they are not the same
  366. if ($share->getShareOwner() !== $share->getSharedBy()) {
  367. $this->sendPermissionUpdate($share);
  368. }
  369. return $share;
  370. }
  371. /**
  372. * send the updated permission to the owner/initiator, if they are not the same
  373. *
  374. * @param IShare $share
  375. * @throws ShareNotFound
  376. * @throws \OC\HintException
  377. */
  378. protected function sendPermissionUpdate(IShare $share) {
  379. $remoteId = $this->getRemoteId($share);
  380. // if the local user is the owner we send the permission change to the initiator
  381. if ($this->userManager->userExists($share->getShareOwner())) {
  382. list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
  383. } else { // ... if not we send the permission change to the owner
  384. list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
  385. }
  386. $this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
  387. }
  388. /**
  389. * update successful reShare with the correct token
  390. *
  391. * @param int $shareId
  392. * @param string $token
  393. */
  394. protected function updateSuccessfulReShare($shareId, $token) {
  395. $query = $this->dbConnection->getQueryBuilder();
  396. $query->update('share')
  397. ->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
  398. ->set('token', $query->createNamedParameter($token))
  399. ->execute();
  400. }
  401. /**
  402. * store remote ID in federated reShare table
  403. *
  404. * @param $shareId
  405. * @param $remoteId
  406. */
  407. public function storeRemoteId($shareId, $remoteId) {
  408. $query = $this->dbConnection->getQueryBuilder();
  409. $query->insert('federated_reshares')
  410. ->values(
  411. [
  412. 'share_id' => $query->createNamedParameter($shareId),
  413. 'remote_id' => $query->createNamedParameter($remoteId),
  414. ]
  415. );
  416. $query->execute();
  417. }
  418. /**
  419. * get share ID on remote server for federated re-shares
  420. *
  421. * @param IShare $share
  422. * @return int
  423. * @throws ShareNotFound
  424. */
  425. public function getRemoteId(IShare $share) {
  426. $query = $this->dbConnection->getQueryBuilder();
  427. $query->select('remote_id')->from('federated_reshares')
  428. ->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
  429. $data = $query->execute()->fetch();
  430. if (!is_array($data) || !isset($data['remote_id'])) {
  431. throw new ShareNotFound();
  432. }
  433. return (int)$data['remote_id'];
  434. }
  435. /**
  436. * @inheritdoc
  437. */
  438. public function move(IShare $share, $recipient) {
  439. /*
  440. * This function does nothing yet as it is just for outgoing
  441. * federated shares.
  442. */
  443. return $share;
  444. }
  445. /**
  446. * Get all children of this share
  447. *
  448. * @param IShare $parent
  449. * @return IShare[]
  450. */
  451. public function getChildren(IShare $parent) {
  452. $children = [];
  453. $qb = $this->dbConnection->getQueryBuilder();
  454. $qb->select('*')
  455. ->from('share')
  456. ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
  457. ->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
  458. ->orderBy('id');
  459. $cursor = $qb->execute();
  460. while($data = $cursor->fetch()) {
  461. $children[] = $this->createShareObject($data);
  462. }
  463. $cursor->closeCursor();
  464. return $children;
  465. }
  466. /**
  467. * Delete a share (owner unShares the file)
  468. *
  469. * @param IShare $share
  470. * @throws ShareNotFound
  471. * @throws \OC\HintException
  472. */
  473. public function delete(IShare $share) {
  474. list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
  475. // if the local user is the owner we can send the unShare request directly...
  476. if ($this->userManager->userExists($share->getShareOwner())) {
  477. $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
  478. $this->revokeShare($share, true);
  479. } else { // ... if not we need to correct ID for the unShare request
  480. $remoteId = $this->getRemoteId($share);
  481. $this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
  482. $this->revokeShare($share, false);
  483. }
  484. // only remove the share when all messages are send to not lose information
  485. // about the share to early
  486. $this->removeShareFromTable($share);
  487. }
  488. /**
  489. * in case of a re-share we need to send the other use (initiator or owner)
  490. * a message that the file was unshared
  491. *
  492. * @param IShare $share
  493. * @param bool $isOwner the user can either be the owner or the user who re-sahred it
  494. * @throws ShareNotFound
  495. * @throws \OC\HintException
  496. */
  497. protected function revokeShare($share, $isOwner) {
  498. if ($this->userManager->userExists($share->getShareOwner() && $this->userManager->userExists($share->getSharedBy()))) {
  499. // If both the owner and the initiator of the share are local users we don't have to notify anybody else
  500. return;
  501. }
  502. // also send a unShare request to the initiator, if this is a different user than the owner
  503. if ($share->getShareOwner() !== $share->getSharedBy()) {
  504. if ($isOwner) {
  505. list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
  506. } else {
  507. list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
  508. }
  509. $remoteId = $this->getRemoteId($share);
  510. $this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
  511. }
  512. }
  513. /**
  514. * remove share from table
  515. *
  516. * @param IShare $share
  517. */
  518. public function removeShareFromTable(IShare $share) {
  519. $this->removeShareFromTableById($share->getId());
  520. }
  521. /**
  522. * remove share from table
  523. *
  524. * @param string $shareId
  525. */
  526. private function removeShareFromTableById($shareId) {
  527. $qb = $this->dbConnection->getQueryBuilder();
  528. $qb->delete('share')
  529. ->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
  530. $qb->execute();
  531. $qb->delete('federated_reshares')
  532. ->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
  533. $qb->execute();
  534. }
  535. /**
  536. * @inheritdoc
  537. */
  538. public function deleteFromSelf(IShare $share, $recipient) {
  539. // nothing to do here. Technically deleteFromSelf in the context of federated
  540. // shares is a umount of a external storage. This is handled here
  541. // apps/files_sharing/lib/external/manager.php
  542. // TODO move this code over to this app
  543. }
  544. public function restore(IShare $share, string $recipient): IShare {
  545. throw new GenericShareException('not implemented');
  546. }
  547. public function getSharesInFolder($userId, Folder $node, $reshares) {
  548. $qb = $this->dbConnection->getQueryBuilder();
  549. $qb->select('*')
  550. ->from('share', 's')
  551. ->andWhere($qb->expr()->orX(
  552. $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
  553. $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
  554. ))
  555. ->andWhere(
  556. $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE))
  557. );
  558. /**
  559. * Reshares for this user are shares where they are the owner.
  560. */
  561. if ($reshares === false) {
  562. $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
  563. } else {
  564. $qb->andWhere(
  565. $qb->expr()->orX(
  566. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  567. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
  568. )
  569. );
  570. }
  571. $qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
  572. $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
  573. $qb->orderBy('id');
  574. $cursor = $qb->execute();
  575. $shares = [];
  576. while ($data = $cursor->fetch()) {
  577. $shares[$data['fileid']][] = $this->createShareObject($data);
  578. }
  579. $cursor->closeCursor();
  580. return $shares;
  581. }
  582. /**
  583. * @inheritdoc
  584. */
  585. public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
  586. $qb = $this->dbConnection->getQueryBuilder();
  587. $qb->select('*')
  588. ->from('share');
  589. $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
  590. /**
  591. * Reshares for this user are shares where they are the owner.
  592. */
  593. if ($reshares === false) {
  594. //Special case for old shares created via the web UI
  595. $or1 = $qb->expr()->andX(
  596. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  597. $qb->expr()->isNull('uid_initiator')
  598. );
  599. $qb->andWhere(
  600. $qb->expr()->orX(
  601. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
  602. $or1
  603. )
  604. );
  605. } else {
  606. $qb->andWhere(
  607. $qb->expr()->orX(
  608. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  609. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
  610. )
  611. );
  612. }
  613. if ($node !== null) {
  614. $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
  615. }
  616. if ($limit !== -1) {
  617. $qb->setMaxResults($limit);
  618. }
  619. $qb->setFirstResult($offset);
  620. $qb->orderBy('id');
  621. $cursor = $qb->execute();
  622. $shares = [];
  623. while($data = $cursor->fetch()) {
  624. $shares[] = $this->createShareObject($data);
  625. }
  626. $cursor->closeCursor();
  627. return $shares;
  628. }
  629. /**
  630. * @inheritdoc
  631. */
  632. public function getShareById($id, $recipientId = null) {
  633. $qb = $this->dbConnection->getQueryBuilder();
  634. $qb->select('*')
  635. ->from('share')
  636. ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
  637. ->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
  638. $cursor = $qb->execute();
  639. $data = $cursor->fetch();
  640. $cursor->closeCursor();
  641. if ($data === false) {
  642. throw new ShareNotFound('Can not find share with ID: ' . $id);
  643. }
  644. try {
  645. $share = $this->createShareObject($data);
  646. } catch (InvalidShare $e) {
  647. throw new ShareNotFound();
  648. }
  649. return $share;
  650. }
  651. /**
  652. * Get shares for a given path
  653. *
  654. * @param \OCP\Files\Node $path
  655. * @return IShare[]
  656. */
  657. public function getSharesByPath(Node $path) {
  658. $qb = $this->dbConnection->getQueryBuilder();
  659. // get federated user shares
  660. $cursor = $qb->select('*')
  661. ->from('share')
  662. ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
  663. ->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
  664. ->execute();
  665. $shares = [];
  666. while($data = $cursor->fetch()) {
  667. $shares[] = $this->createShareObject($data);
  668. }
  669. $cursor->closeCursor();
  670. return $shares;
  671. }
  672. /**
  673. * @inheritdoc
  674. */
  675. public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
  676. /** @var IShare[] $shares */
  677. $shares = [];
  678. //Get shares directly with this user
  679. $qb = $this->dbConnection->getQueryBuilder();
  680. $qb->select('*')
  681. ->from('share');
  682. // Order by id
  683. $qb->orderBy('id');
  684. // Set limit and offset
  685. if ($limit !== -1) {
  686. $qb->setMaxResults($limit);
  687. }
  688. $qb->setFirstResult($offset);
  689. $qb->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
  690. $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
  691. // Filter by node if provided
  692. if ($node !== null) {
  693. $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
  694. }
  695. $cursor = $qb->execute();
  696. while($data = $cursor->fetch()) {
  697. $shares[] = $this->createShareObject($data);
  698. }
  699. $cursor->closeCursor();
  700. return $shares;
  701. }
  702. /**
  703. * Get a share by token
  704. *
  705. * @param string $token
  706. * @return IShare
  707. * @throws ShareNotFound
  708. */
  709. public function getShareByToken($token) {
  710. $qb = $this->dbConnection->getQueryBuilder();
  711. $cursor = $qb->select('*')
  712. ->from('share')
  713. ->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
  714. ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
  715. ->execute();
  716. $data = $cursor->fetch();
  717. if ($data === false) {
  718. throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
  719. }
  720. try {
  721. $share = $this->createShareObject($data);
  722. } catch (InvalidShare $e) {
  723. throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
  724. }
  725. return $share;
  726. }
  727. /**
  728. * get database row of a give share
  729. *
  730. * @param $id
  731. * @return array
  732. * @throws ShareNotFound
  733. */
  734. private function getRawShare($id) {
  735. // Now fetch the inserted share and create a complete share object
  736. $qb = $this->dbConnection->getQueryBuilder();
  737. $qb->select('*')
  738. ->from('share')
  739. ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
  740. $cursor = $qb->execute();
  741. $data = $cursor->fetch();
  742. $cursor->closeCursor();
  743. if ($data === false) {
  744. throw new ShareNotFound;
  745. }
  746. return $data;
  747. }
  748. /**
  749. * Create a share object from an database row
  750. *
  751. * @param array $data
  752. * @return IShare
  753. * @throws InvalidShare
  754. * @throws ShareNotFound
  755. */
  756. private function createShareObject($data) {
  757. $share = new Share($this->rootFolder, $this->userManager);
  758. $share->setId((int)$data['id'])
  759. ->setShareType((int)$data['share_type'])
  760. ->setPermissions((int)$data['permissions'])
  761. ->setTarget($data['file_target'])
  762. ->setMailSend((bool)$data['mail_send'])
  763. ->setToken($data['token']);
  764. $shareTime = new \DateTime();
  765. $shareTime->setTimestamp((int)$data['stime']);
  766. $share->setShareTime($shareTime);
  767. $share->setSharedWith($data['share_with']);
  768. if ($data['uid_initiator'] !== null) {
  769. $share->setShareOwner($data['uid_owner']);
  770. $share->setSharedBy($data['uid_initiator']);
  771. } else {
  772. //OLD SHARE
  773. $share->setSharedBy($data['uid_owner']);
  774. $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
  775. $owner = $path->getOwner();
  776. $share->setShareOwner($owner->getUID());
  777. }
  778. $share->setNodeId((int)$data['file_source']);
  779. $share->setNodeType($data['item_type']);
  780. $share->setProviderId($this->identifier());
  781. return $share;
  782. }
  783. /**
  784. * Get the node with file $id for $user
  785. *
  786. * @param string $userId
  787. * @param int $id
  788. * @return \OCP\Files\File|\OCP\Files\Folder
  789. * @throws InvalidShare
  790. */
  791. private function getNode($userId, $id) {
  792. try {
  793. $userFolder = $this->rootFolder->getUserFolder($userId);
  794. } catch (NotFoundException $e) {
  795. throw new InvalidShare();
  796. }
  797. $nodes = $userFolder->getById($id);
  798. if (empty($nodes)) {
  799. throw new InvalidShare();
  800. }
  801. return $nodes[0];
  802. }
  803. /**
  804. * A user is deleted from the system
  805. * So clean up the relevant shares.
  806. *
  807. * @param string $uid
  808. * @param int $shareType
  809. */
  810. public function userDeleted($uid, $shareType) {
  811. //TODO: probabaly a good idea to send unshare info to remote servers
  812. $qb = $this->dbConnection->getQueryBuilder();
  813. $qb->delete('share')
  814. ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
  815. ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
  816. ->execute();
  817. }
  818. /**
  819. * This provider does not handle groups
  820. *
  821. * @param string $gid
  822. */
  823. public function groupDeleted($gid) {
  824. // We don't handle groups here
  825. }
  826. /**
  827. * This provider does not handle groups
  828. *
  829. * @param string $uid
  830. * @param string $gid
  831. */
  832. public function userDeletedFromGroup($uid, $gid) {
  833. // We don't handle groups here
  834. }
  835. /**
  836. * check if users from other Nextcloud instances are allowed to mount public links share by this instance
  837. *
  838. * @return bool
  839. */
  840. public function isOutgoingServer2serverShareEnabled() {
  841. if ($this->gsConfig->onlyInternalFederation()) {
  842. return false;
  843. }
  844. $result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
  845. return ($result === 'yes');
  846. }
  847. /**
  848. * check if users are allowed to mount public links from other Nextclouds
  849. *
  850. * @return bool
  851. */
  852. public function isIncomingServer2serverShareEnabled() {
  853. if ($this->gsConfig->onlyInternalFederation()) {
  854. return false;
  855. }
  856. $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
  857. return ($result === 'yes');
  858. }
  859. /**
  860. * check if users from other Nextcloud instances are allowed to send federated group shares
  861. *
  862. * @return bool
  863. */
  864. public function isOutgoingServer2serverGroupShareEnabled() {
  865. if ($this->gsConfig->onlyInternalFederation()) {
  866. return false;
  867. }
  868. $result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no');
  869. return ($result === 'yes');
  870. }
  871. /**
  872. * check if users are allowed to receive federated group shares
  873. *
  874. * @return bool
  875. */
  876. public function isIncomingServer2serverGroupShareEnabled() {
  877. if ($this->gsConfig->onlyInternalFederation()) {
  878. return false;
  879. }
  880. $result = $this->config->getAppValue('files_sharing', 'incoming_server2server_group_share_enabled', 'no');
  881. return ($result === 'yes');
  882. }
  883. /**
  884. * check if federated group sharing is supported, therefore the OCM API need to be enabled
  885. *
  886. * @return bool
  887. */
  888. public function isFederatedGroupSharingSupported() {
  889. return $this->cloudFederationProviderManager->isReady();
  890. }
  891. /**
  892. * Check if querying sharees on the lookup server is enabled
  893. *
  894. * @return bool
  895. */
  896. public function isLookupServerQueriesEnabled() {
  897. // in a global scale setup we should always query the lookup server
  898. if ($this->gsConfig->isGlobalScaleEnabled()) {
  899. return true;
  900. }
  901. $result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
  902. return ($result === 'yes');
  903. }
  904. /**
  905. * Check if it is allowed to publish user specific data to the lookup server
  906. *
  907. * @return bool
  908. */
  909. public function isLookupServerUploadEnabled() {
  910. // in a global scale setup the admin is responsible to keep the lookup server up-to-date
  911. if ($this->gsConfig->isGlobalScaleEnabled()) {
  912. return false;
  913. }
  914. $result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
  915. return ($result === 'yes');
  916. }
  917. /**
  918. * @inheritdoc
  919. */
  920. public function getAccessList($nodes, $currentAccess) {
  921. $ids = [];
  922. foreach ($nodes as $node) {
  923. $ids[] = $node->getId();
  924. }
  925. $qb = $this->dbConnection->getQueryBuilder();
  926. $qb->select('share_with', 'token', 'file_source')
  927. ->from('share')
  928. ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
  929. ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
  930. ->andWhere($qb->expr()->orX(
  931. $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
  932. $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
  933. ));
  934. $cursor = $qb->execute();
  935. if ($currentAccess === false) {
  936. $remote = $cursor->fetch() !== false;
  937. $cursor->closeCursor();
  938. return ['remote' => $remote];
  939. }
  940. $remote = [];
  941. while ($row = $cursor->fetch()) {
  942. $remote[$row['share_with']] = [
  943. 'node_id' => $row['file_source'],
  944. 'token' => $row['token'],
  945. ];
  946. }
  947. $cursor->closeCursor();
  948. return ['remote' => $remote];
  949. }
  950. public function getAllShares(): iterable {
  951. $qb = $this->dbConnection->getQueryBuilder();
  952. $qb->select('*')
  953. ->from('share')
  954. ->where(
  955. $qb->expr()->orX(
  956. $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE)),
  957. $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE_GROUP))
  958. )
  959. );
  960. $cursor = $qb->execute();
  961. while($data = $cursor->fetch()) {
  962. try {
  963. $share = $this->createShareObject($data);
  964. } catch (InvalidShare $e) {
  965. continue;
  966. } catch (ShareNotFound $e) {
  967. continue;
  968. }
  969. yield $share;
  970. }
  971. $cursor->closeCursor();
  972. }
  973. }