federatedshareprovider.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. /**
  3. * @author Björn Schießle <schiessle@owncloud.com>
  4. * @author Roeland Jago Douma <rullzer@owncloud.com>
  5. * @author Thomas Müller <thomas.mueller@tmit.eu>
  6. *
  7. * @copyright Copyright (c) 2016, ownCloud, Inc.
  8. * @license AGPL-3.0
  9. *
  10. * This code is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License, version 3,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License, version 3,
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>
  21. *
  22. */
  23. namespace OCA\FederatedFileSharing;
  24. use OC\Share20\Share;
  25. use OCP\Files\IRootFolder;
  26. use OCP\IL10N;
  27. use OCP\ILogger;
  28. use OCP\Share\IShare;
  29. use OCP\Share\IShareProvider;
  30. use OC\Share20\Exception\InvalidShare;
  31. use OCP\Share\Exceptions\ShareNotFound;
  32. use OCP\Files\NotFoundException;
  33. use OCP\IDBConnection;
  34. use OCP\Files\Node;
  35. /**
  36. * Class FederatedShareProvider
  37. *
  38. * @package OCA\FederatedFileSharing
  39. */
  40. class FederatedShareProvider implements IShareProvider {
  41. const SHARE_TYPE_REMOTE = 6;
  42. /** @var IDBConnection */
  43. private $dbConnection;
  44. /** @var AddressHandler */
  45. private $addressHandler;
  46. /** @var Notifications */
  47. private $notifications;
  48. /** @var TokenHandler */
  49. private $tokenHandler;
  50. /** @var IL10N */
  51. private $l;
  52. /** @var ILogger */
  53. private $logger;
  54. /** @var IRootFolder */
  55. private $rootFolder;
  56. /**
  57. * DefaultShareProvider constructor.
  58. *
  59. * @param IDBConnection $connection
  60. * @param AddressHandler $addressHandler
  61. * @param Notifications $notifications
  62. * @param TokenHandler $tokenHandler
  63. * @param IL10N $l10n
  64. * @param ILogger $logger
  65. * @param IRootFolder $rootFolder
  66. */
  67. public function __construct(
  68. IDBConnection $connection,
  69. AddressHandler $addressHandler,
  70. Notifications $notifications,
  71. TokenHandler $tokenHandler,
  72. IL10N $l10n,
  73. ILogger $logger,
  74. IRootFolder $rootFolder
  75. ) {
  76. $this->dbConnection = $connection;
  77. $this->addressHandler = $addressHandler;
  78. $this->notifications = $notifications;
  79. $this->tokenHandler = $tokenHandler;
  80. $this->l = $l10n;
  81. $this->logger = $logger;
  82. $this->rootFolder = $rootFolder;
  83. }
  84. /**
  85. * Return the identifier of this provider.
  86. *
  87. * @return string Containing only [a-zA-Z0-9]
  88. */
  89. public function identifier() {
  90. return 'ocFederatedSharing';
  91. }
  92. /**
  93. * Share a path
  94. *
  95. * @param IShare $share
  96. * @return IShare The share object
  97. * @throws ShareNotFound
  98. * @throws \Exception
  99. */
  100. public function create(IShare $share) {
  101. $shareWith = $share->getSharedWith();
  102. $itemSource = $share->getNodeId();
  103. $itemType = $share->getNodeType();
  104. $uidOwner = $share->getShareOwner();
  105. $permissions = $share->getPermissions();
  106. $sharedBy = $share->getSharedBy();
  107. /*
  108. * Check if file is not already shared with the remote user
  109. */
  110. $alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
  111. if (!empty($alreadyShared)) {
  112. $message = 'Sharing %s failed, because this item is already shared with %s';
  113. $message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
  114. $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
  115. throw new \Exception($message_t);
  116. }
  117. // don't allow federated shares if source and target server are the same
  118. list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
  119. $currentServer = $this->addressHandler->generateRemoteURL();
  120. $currentUser = $sharedBy;
  121. if ($this->addressHandler->compareAddresses($user, $remote, $currentUser, $currentServer)) {
  122. $message = 'Not allowed to create a federated share with the same user.';
  123. $message_t = $this->l->t('Not allowed to create a federated share with the same user');
  124. $this->logger->debug($message, ['app' => 'Federated File Sharing']);
  125. throw new \Exception($message_t);
  126. }
  127. $token = $this->tokenHandler->generateToken();
  128. $shareWith = $user . '@' . $remote;
  129. $shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
  130. $send = $this->notifications->sendRemoteShare(
  131. $token,
  132. $shareWith,
  133. $share->getNode()->getName(),
  134. $shareId,
  135. $share->getSharedBy()
  136. );
  137. $data = $this->getRawShare($shareId);
  138. $share = $this->createShare($data);
  139. if ($send === false) {
  140. $this->delete($share);
  141. $message_t = $this->l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.',
  142. [$share->getNode()->getName(), $shareWith]);
  143. throw new \Exception($message_t);
  144. }
  145. return $share;
  146. }
  147. /**
  148. * add share to the database and return the ID
  149. *
  150. * @param int $itemSource
  151. * @param string $itemType
  152. * @param string $shareWith
  153. * @param string $sharedBy
  154. * @param string $uidOwner
  155. * @param int $permissions
  156. * @param string $token
  157. * @return int
  158. */
  159. private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
  160. $qb = $this->dbConnection->getQueryBuilder();
  161. $qb->insert('share')
  162. ->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
  163. ->setValue('item_type', $qb->createNamedParameter($itemType))
  164. ->setValue('item_source', $qb->createNamedParameter($itemSource))
  165. ->setValue('file_source', $qb->createNamedParameter($itemSource))
  166. ->setValue('share_with', $qb->createNamedParameter($shareWith))
  167. ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
  168. ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
  169. ->setValue('permissions', $qb->createNamedParameter($permissions))
  170. ->setValue('token', $qb->createNamedParameter($token))
  171. ->setValue('stime', $qb->createNamedParameter(time()));
  172. /*
  173. * Added to fix https://github.com/owncloud/core/issues/22215
  174. * Can be removed once we get rid of ajax/share.php
  175. */
  176. $qb->setValue('file_target', $qb->createNamedParameter(''));
  177. $qb->execute();
  178. $id = $qb->getLastInsertId();
  179. return (int)$id;
  180. }
  181. /**
  182. * Update a share
  183. *
  184. * @param IShare $share
  185. * @return IShare The share object
  186. */
  187. public function update(IShare $share) {
  188. /*
  189. * We allow updating the permissions of federated shares
  190. */
  191. $qb = $this->dbConnection->getQueryBuilder();
  192. $qb->update('share')
  193. ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
  194. ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
  195. ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
  196. ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
  197. ->execute();
  198. return $share;
  199. }
  200. /**
  201. * @inheritdoc
  202. */
  203. public function move(IShare $share, $recipient) {
  204. /*
  205. * This function does nothing yet as it is just for outgoing
  206. * federated shares.
  207. */
  208. return $share;
  209. }
  210. /**
  211. * Get all children of this share
  212. *
  213. * @param IShare $parent
  214. * @return IShare[]
  215. */
  216. public function getChildren(IShare $parent) {
  217. $children = [];
  218. $qb = $this->dbConnection->getQueryBuilder();
  219. $qb->select('*')
  220. ->from('share')
  221. ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
  222. ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
  223. ->orderBy('id');
  224. $cursor = $qb->execute();
  225. while($data = $cursor->fetch()) {
  226. $children[] = $this->createShare($data);
  227. }
  228. $cursor->closeCursor();
  229. return $children;
  230. }
  231. /**
  232. * Delete a share
  233. *
  234. * @param IShare $share
  235. */
  236. public function delete(IShare $share) {
  237. $qb = $this->dbConnection->getQueryBuilder();
  238. $qb->delete('share')
  239. ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
  240. $qb->execute();
  241. list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
  242. $this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
  243. }
  244. /**
  245. * @inheritdoc
  246. */
  247. public function deleteFromSelf(IShare $share, $recipient) {
  248. // nothing to do here. Technically deleteFromSelf in the context of federated
  249. // shares is a umount of a external storage. This is handled here
  250. // apps/files_sharing/lib/external/manager.php
  251. // TODO move this code over to this app
  252. return;
  253. }
  254. /**
  255. * @inheritdoc
  256. */
  257. public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
  258. $qb = $this->dbConnection->getQueryBuilder();
  259. $qb->select('*')
  260. ->from('share');
  261. $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
  262. /**
  263. * Reshares for this user are shares where they are the owner.
  264. */
  265. if ($reshares === false) {
  266. //Special case for old shares created via the web UI
  267. $or1 = $qb->expr()->andX(
  268. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  269. $qb->expr()->isNull('uid_initiator')
  270. );
  271. $qb->andWhere(
  272. $qb->expr()->orX(
  273. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
  274. $or1
  275. )
  276. );
  277. } else {
  278. $qb->andWhere(
  279. $qb->expr()->orX(
  280. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  281. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
  282. )
  283. );
  284. }
  285. if ($node !== null) {
  286. $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
  287. }
  288. if ($limit !== -1) {
  289. $qb->setMaxResults($limit);
  290. }
  291. $qb->setFirstResult($offset);
  292. $qb->orderBy('id');
  293. $cursor = $qb->execute();
  294. $shares = [];
  295. while($data = $cursor->fetch()) {
  296. $shares[] = $this->createShare($data);
  297. }
  298. $cursor->closeCursor();
  299. return $shares;
  300. }
  301. /**
  302. * @inheritdoc
  303. */
  304. public function getShareById($id, $recipientId = null) {
  305. $qb = $this->dbConnection->getQueryBuilder();
  306. $qb->select('*')
  307. ->from('share')
  308. ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
  309. ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
  310. $cursor = $qb->execute();
  311. $data = $cursor->fetch();
  312. $cursor->closeCursor();
  313. if ($data === false) {
  314. throw new ShareNotFound();
  315. }
  316. try {
  317. $share = $this->createShare($data);
  318. } catch (InvalidShare $e) {
  319. throw new ShareNotFound();
  320. }
  321. return $share;
  322. }
  323. /**
  324. * Get shares for a given path
  325. *
  326. * @param \OCP\Files\Node $path
  327. * @return IShare[]
  328. */
  329. public function getSharesByPath(Node $path) {
  330. $qb = $this->dbConnection->getQueryBuilder();
  331. $cursor = $qb->select('*')
  332. ->from('share')
  333. ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
  334. ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
  335. ->execute();
  336. $shares = [];
  337. while($data = $cursor->fetch()) {
  338. $shares[] = $this->createShare($data);
  339. }
  340. $cursor->closeCursor();
  341. return $shares;
  342. }
  343. /**
  344. * @inheritdoc
  345. */
  346. public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
  347. /** @var IShare[] $shares */
  348. $shares = [];
  349. //Get shares directly with this user
  350. $qb = $this->dbConnection->getQueryBuilder();
  351. $qb->select('*')
  352. ->from('share');
  353. // Order by id
  354. $qb->orderBy('id');
  355. // Set limit and offset
  356. if ($limit !== -1) {
  357. $qb->setMaxResults($limit);
  358. }
  359. $qb->setFirstResult($offset);
  360. $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
  361. $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
  362. // Filter by node if provided
  363. if ($node !== null) {
  364. $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
  365. }
  366. $cursor = $qb->execute();
  367. while($data = $cursor->fetch()) {
  368. $shares[] = $this->createShare($data);
  369. }
  370. $cursor->closeCursor();
  371. return $shares;
  372. }
  373. /**
  374. * Get a share by token
  375. *
  376. * @param string $token
  377. * @return IShare
  378. * @throws ShareNotFound
  379. */
  380. public function getShareByToken($token) {
  381. $qb = $this->dbConnection->getQueryBuilder();
  382. $cursor = $qb->select('*')
  383. ->from('share')
  384. ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
  385. ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
  386. ->execute();
  387. $data = $cursor->fetch();
  388. if ($data === false) {
  389. throw new ShareNotFound();
  390. }
  391. try {
  392. $share = $this->createShare($data);
  393. } catch (InvalidShare $e) {
  394. throw new ShareNotFound();
  395. }
  396. return $share;
  397. }
  398. /**
  399. * get database row of a give share
  400. *
  401. * @param $id
  402. * @return array
  403. * @throws ShareNotFound
  404. */
  405. private function getRawShare($id) {
  406. // Now fetch the inserted share and create a complete share object
  407. $qb = $this->dbConnection->getQueryBuilder();
  408. $qb->select('*')
  409. ->from('share')
  410. ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
  411. $cursor = $qb->execute();
  412. $data = $cursor->fetch();
  413. $cursor->closeCursor();
  414. if ($data === false) {
  415. throw new ShareNotFound;
  416. }
  417. return $data;
  418. }
  419. /**
  420. * Create a share object from an database row
  421. *
  422. * @param array $data
  423. * @return IShare
  424. * @throws InvalidShare
  425. * @throws ShareNotFound
  426. */
  427. private function createShare($data) {
  428. $share = new Share($this->rootFolder);
  429. $share->setId((int)$data['id'])
  430. ->setShareType((int)$data['share_type'])
  431. ->setPermissions((int)$data['permissions'])
  432. ->setTarget($data['file_target'])
  433. ->setMailSend((bool)$data['mail_send'])
  434. ->setToken($data['token']);
  435. $shareTime = new \DateTime();
  436. $shareTime->setTimestamp((int)$data['stime']);
  437. $share->setShareTime($shareTime);
  438. $share->setSharedWith($data['share_with']);
  439. if ($data['uid_initiator'] !== null) {
  440. $share->setShareOwner($data['uid_owner']);
  441. $share->setSharedBy($data['uid_initiator']);
  442. } else {
  443. //OLD SHARE
  444. $share->setSharedBy($data['uid_owner']);
  445. $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
  446. $owner = $path->getOwner();
  447. $share->setShareOwner($owner->getUID());
  448. }
  449. $share->setNodeId((int)$data['file_source']);
  450. $share->setNodeType($data['item_type']);
  451. $share->setProviderId($this->identifier());
  452. return $share;
  453. }
  454. /**
  455. * Get the node with file $id for $user
  456. *
  457. * @param string $userId
  458. * @param int $id
  459. * @return \OCP\Files\File|\OCP\Files\Folder
  460. * @throws InvalidShare
  461. */
  462. private function getNode($userId, $id) {
  463. try {
  464. $userFolder = $this->rootFolder->getUserFolder($userId);
  465. } catch (NotFoundException $e) {
  466. throw new InvalidShare();
  467. }
  468. $nodes = $userFolder->getById($id);
  469. if (empty($nodes)) {
  470. throw new InvalidShare();
  471. }
  472. return $nodes[0];
  473. }
  474. /**
  475. * A user is deleted from the system
  476. * So clean up the relevant shares.
  477. *
  478. * @param string $uid
  479. * @param int $shareType
  480. */
  481. public function userDeleted($uid, $shareType) {
  482. //TODO: probabaly a good idea to send unshare info to remote servers
  483. $qb = $this->dbConnection->getQueryBuilder();
  484. $qb->delete('share')
  485. ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
  486. ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
  487. ->execute();
  488. }
  489. /**
  490. * This provider does not handle groups
  491. *
  492. * @param string $gid
  493. */
  494. public function groupDeleted($gid) {
  495. // We don't handle groups here
  496. return;
  497. }
  498. /**
  499. * This provider does not handle groups
  500. *
  501. * @param string $uid
  502. * @param string $gid
  503. */
  504. public function userDeletedFromGroup($uid, $gid) {
  505. // We don't handle groups here
  506. return;
  507. }
  508. }