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.

преди 10 години
преди 7 години
преди 8 години
преди 7 години
преди 9 години
преди 8 години
преди 9 години
преди 7 години
преди 7 години
преди 8 години
преди 9 години
преди 9 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 10 години
преди 8 години
преди 9 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bjoern Schiessle <bjoern@schiessle.org>
  6. * @author Björn Schießle <bjoern@schiessle.org>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Daniel Hansson <daniel@techandme.se>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  11. * @author Julius Härtl <jus@bitgrid.net>
  12. * @author Lukas Reschke <lukas@statuscode.ch>
  13. * @author Morris Jobke <hey@morrisjobke.de>
  14. * @author Robin Appelman <robin@icewind.nl>
  15. * @author Roeland Jago Douma <roeland@famdouma.nl>
  16. * @author Stefan Weil <sw@weilnetz.de>
  17. *
  18. * @license AGPL-3.0
  19. *
  20. * This code is free software: you can redistribute it and/or modify
  21. * it under the terms of the GNU Affero General Public License, version 3,
  22. * as published by the Free Software Foundation.
  23. *
  24. * This program is distributed in the hope that it will be useful,
  25. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. * GNU Affero General Public License for more details.
  28. *
  29. * You should have received a copy of the GNU Affero General Public License, version 3,
  30. * along with this program. If not, see <http://www.gnu.org/licenses/>
  31. *
  32. */
  33. namespace OCA\Files_Sharing\External;
  34. use OC\Files\Filesystem;
  35. use OCA\FederatedFileSharing\Events\FederatedShareAddedEvent;
  36. use OCA\Files_Sharing\Helper;
  37. use OCP\EventDispatcher\IEventDispatcher;
  38. use OCP\Federation\ICloudFederationFactory;
  39. use OCP\Federation\ICloudFederationProviderManager;
  40. use OCP\Files;
  41. use OCP\Files\Storage\IStorageFactory;
  42. use OCP\Http\Client\IClientService;
  43. use OCP\IDBConnection;
  44. use OCP\IGroupManager;
  45. use OCP\IUserManager;
  46. use OCP\Notification\IManager;
  47. use OCP\OCS\IDiscoveryService;
  48. use OCP\Share;
  49. use OCP\Share\IShare;
  50. class Manager {
  51. public const STORAGE = '\OCA\Files_Sharing\External\Storage';
  52. /** @var string|null */
  53. private $uid;
  54. /** @var IDBConnection */
  55. private $connection;
  56. /** @var \OC\Files\Mount\Manager */
  57. private $mountManager;
  58. /** @var IStorageFactory */
  59. private $storageLoader;
  60. /** @var IClientService */
  61. private $clientService;
  62. /** @var IManager */
  63. private $notificationManager;
  64. /** @var IDiscoveryService */
  65. private $discoveryService;
  66. /** @var ICloudFederationProviderManager */
  67. private $cloudFederationProviderManager;
  68. /** @var ICloudFederationFactory */
  69. private $cloudFederationFactory;
  70. /** @var IGroupManager */
  71. private $groupManager;
  72. /** @var IUserManager */
  73. private $userManager;
  74. /** @var IEventDispatcher */
  75. private $eventDispatcher;
  76. public function __construct(IDBConnection $connection,
  77. \OC\Files\Mount\Manager $mountManager,
  78. IStorageFactory $storageLoader,
  79. IClientService $clientService,
  80. IManager $notificationManager,
  81. IDiscoveryService $discoveryService,
  82. ICloudFederationProviderManager $cloudFederationProviderManager,
  83. ICloudFederationFactory $cloudFederationFactory,
  84. IGroupManager $groupManager,
  85. IUserManager $userManager,
  86. ?string $uid,
  87. IEventDispatcher $eventDispatcher) {
  88. $this->connection = $connection;
  89. $this->mountManager = $mountManager;
  90. $this->storageLoader = $storageLoader;
  91. $this->clientService = $clientService;
  92. $this->uid = $uid;
  93. $this->notificationManager = $notificationManager;
  94. $this->discoveryService = $discoveryService;
  95. $this->cloudFederationProviderManager = $cloudFederationProviderManager;
  96. $this->cloudFederationFactory = $cloudFederationFactory;
  97. $this->groupManager = $groupManager;
  98. $this->userManager = $userManager;
  99. $this->eventDispatcher = $eventDispatcher;
  100. }
  101. /**
  102. * add new server-to-server share
  103. *
  104. * @param string $remote
  105. * @param string $token
  106. * @param string $password
  107. * @param string $name
  108. * @param string $owner
  109. * @param int $shareType
  110. * @param boolean $accepted
  111. * @param string $user
  112. * @param int $remoteId
  113. * @param int $parent
  114. * @return Mount|null
  115. * @throws \Doctrine\DBAL\DBALException
  116. */
  117. public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = -1, $parent = -1) {
  118. $user = $user ? $user : $this->uid;
  119. $accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING;
  120. $name = Filesystem::normalizePath('/' . $name);
  121. if ($accepted !== IShare::STATUS_ACCEPTED) {
  122. // To avoid conflicts with the mount point generation later,
  123. // we only use a temporary mount point name here. The real
  124. // mount point name will be generated when accepting the share,
  125. // using the original share item name.
  126. $tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
  127. $mountPoint = $tmpMountPointName;
  128. $hash = md5($tmpMountPointName);
  129. $data = [
  130. 'remote' => $remote,
  131. 'share_token' => $token,
  132. 'password' => $password,
  133. 'name' => $name,
  134. 'owner' => $owner,
  135. 'user' => $user,
  136. 'mountpoint' => $mountPoint,
  137. 'mountpoint_hash' => $hash,
  138. 'accepted' => $accepted,
  139. 'remote_id' => $remoteId,
  140. 'share_type' => $shareType,
  141. ];
  142. $i = 1;
  143. while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
  144. // The external share already exists for the user
  145. $data['mountpoint'] = $tmpMountPointName . '-' . $i;
  146. $data['mountpoint_hash'] = md5($data['mountpoint']);
  147. $i++;
  148. }
  149. return null;
  150. }
  151. $mountPoint = Files::buildNotExistingFileName('/', $name);
  152. $mountPoint = Filesystem::normalizePath('/' . $mountPoint);
  153. $hash = md5($mountPoint);
  154. $this->writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType);
  155. $options = [
  156. 'remote' => $remote,
  157. 'token' => $token,
  158. 'password' => $password,
  159. 'mountpoint' => $mountPoint,
  160. 'owner' => $owner
  161. ];
  162. return $this->mountShare($options);
  163. }
  164. /**
  165. * write remote share to the database
  166. *
  167. * @param $remote
  168. * @param $token
  169. * @param $password
  170. * @param $name
  171. * @param $owner
  172. * @param $user
  173. * @param $mountPoint
  174. * @param $hash
  175. * @param $accepted
  176. * @param $remoteId
  177. * @param $parent
  178. * @param $shareType
  179. * @return bool
  180. */
  181. private function writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType) {
  182. $query = $this->connection->prepare('
  183. INSERT INTO `*PREFIX*share_external`
  184. (`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`, `parent`, `share_type`)
  185. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  186. ');
  187. return $query->execute([$remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType]);
  188. }
  189. /**
  190. * get share
  191. *
  192. * @param int $id share id
  193. * @return mixed share of false
  194. */
  195. public function getShare($id) {
  196. $getShare = $this->connection->prepare('
  197. SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash`
  198. FROM `*PREFIX*share_external`
  199. WHERE `id` = ?');
  200. $result = $getShare->execute([$id]);
  201. $share = $result ? $getShare->fetch() : [];
  202. $validShare = is_array($share) && isset($share['share_type']) && isset($share['user']);
  203. // check if the user is allowed to access it
  204. if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) {
  205. return $share;
  206. } elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) {
  207. $user = $this->userManager->get($this->uid);
  208. if ($this->groupManager->get($share['user'])->inGroup($user)) {
  209. return $share;
  210. }
  211. }
  212. return false;
  213. }
  214. /**
  215. * accept server-to-server share
  216. *
  217. * @param int $id
  218. * @return bool True if the share could be accepted, false otherwise
  219. */
  220. public function acceptShare($id) {
  221. $share = $this->getShare($id);
  222. $result = false;
  223. if ($share) {
  224. \OC_Util::setupFS($this->uid);
  225. $shareFolder = Helper::getShareFolder();
  226. $mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']);
  227. $mountPoint = Filesystem::normalizePath($mountPoint);
  228. $hash = md5($mountPoint);
  229. $userShareAccepted = false;
  230. if ((int)$share['share_type'] === IShare::TYPE_USER) {
  231. $acceptShare = $this->connection->prepare('
  232. UPDATE `*PREFIX*share_external`
  233. SET `accepted` = ?,
  234. `mountpoint` = ?,
  235. `mountpoint_hash` = ?
  236. WHERE `id` = ? AND `user` = ?');
  237. $userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]);
  238. } else {
  239. $result = $this->writeShareToDb(
  240. $share['remote'],
  241. $share['share_token'],
  242. $share['password'],
  243. $share['name'],
  244. $share['owner'],
  245. $this->uid,
  246. $mountPoint, $hash, 1,
  247. $share['remote_id'],
  248. $id,
  249. $share['share_type']);
  250. }
  251. if ($userShareAccepted === true) {
  252. $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
  253. $event = new FederatedShareAddedEvent($share['remote']);
  254. $this->eventDispatcher->dispatchTyped($event);
  255. $result = true;
  256. }
  257. }
  258. // Make sure the user has no notification for something that does not exist anymore.
  259. $this->processNotification($id);
  260. return $result;
  261. }
  262. /**
  263. * decline server-to-server share
  264. *
  265. * @param int $id
  266. * @return bool True if the share could be declined, false otherwise
  267. */
  268. public function declineShare($id) {
  269. $share = $this->getShare($id);
  270. $result = false;
  271. if ($share && (int)$share['share_type'] === IShare::TYPE_USER) {
  272. $removeShare = $this->connection->prepare('
  273. DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
  274. $removeShare->execute([$id, $this->uid]);
  275. $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
  276. $this->processNotification($id);
  277. $result = true;
  278. } elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) {
  279. $result = $this->writeShareToDb(
  280. $share['remote'],
  281. $share['share_token'],
  282. $share['password'],
  283. $share['name'],
  284. $share['owner'],
  285. $this->uid,
  286. $share['mountpoint'],
  287. $share['mountpoint_hash'],
  288. 0,
  289. $share['remote_id'],
  290. $id,
  291. $share['share_type']);
  292. $this->processNotification($id);
  293. }
  294. return $result;
  295. }
  296. /**
  297. * @param int $remoteShare
  298. */
  299. public function processNotification($remoteShare) {
  300. $filter = $this->notificationManager->createNotification();
  301. $filter->setApp('files_sharing')
  302. ->setUser($this->uid)
  303. ->setObject('remote_share', (int) $remoteShare);
  304. $this->notificationManager->markProcessed($filter);
  305. }
  306. /**
  307. * inform remote server whether server-to-server share was accepted/declined
  308. *
  309. * @param string $remote
  310. * @param string $token
  311. * @param int $remoteId Share id on the remote host
  312. * @param string $feedback
  313. * @return boolean
  314. */
  315. private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
  316. $result = $this->tryOCMEndPoint($remote, $token, $remoteId, $feedback);
  317. if ($result === true) {
  318. return true;
  319. }
  320. $federationEndpoints = $this->discoveryService->discover($remote, 'FEDERATED_SHARING');
  321. $endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
  322. $url = rtrim($remote, '/') . $endpoint . '/' . $remoteId . '/' . $feedback . '?format=' . Share::RESPONSE_FORMAT;
  323. $fields = ['token' => $token];
  324. $client = $this->clientService->newClient();
  325. try {
  326. $response = $client->post(
  327. $url,
  328. [
  329. 'body' => $fields,
  330. 'connect_timeout' => 10,
  331. ]
  332. );
  333. } catch (\Exception $e) {
  334. return false;
  335. }
  336. $status = json_decode($response->getBody(), true);
  337. return ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200);
  338. }
  339. /**
  340. * try send accept message to ocm end-point
  341. *
  342. * @param string $remoteDomain
  343. * @param string $token
  344. * @param $remoteId id of the share
  345. * @param string $feedback
  346. * @return bool
  347. */
  348. protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) {
  349. switch ($feedback) {
  350. case 'accept':
  351. $notification = $this->cloudFederationFactory->getCloudFederationNotification();
  352. $notification->setMessage(
  353. 'SHARE_ACCEPTED',
  354. 'file',
  355. $remoteId,
  356. [
  357. 'sharedSecret' => $token,
  358. 'message' => 'Recipient accept the share'
  359. ]
  360. );
  361. return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
  362. case 'decline':
  363. $notification = $this->cloudFederationFactory->getCloudFederationNotification();
  364. $notification->setMessage(
  365. 'SHARE_DECLINED',
  366. 'file',
  367. $remoteId,
  368. [
  369. 'sharedSecret' => $token,
  370. 'message' => 'Recipient declined the share'
  371. ]
  372. );
  373. return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
  374. }
  375. return false;
  376. }
  377. /**
  378. * remove '/user/files' from the path and trailing slashes
  379. *
  380. * @param string $path
  381. * @return string
  382. */
  383. protected function stripPath($path) {
  384. $prefix = '/' . $this->uid . '/files';
  385. return rtrim(substr($path, strlen($prefix)), '/');
  386. }
  387. public function getMount($data) {
  388. $data['manager'] = $this;
  389. $mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
  390. $data['mountpoint'] = $mountPoint;
  391. $data['certificateManager'] = \OC::$server->getCertificateManager($this->uid);
  392. return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
  393. }
  394. /**
  395. * @param array $data
  396. * @return Mount
  397. */
  398. protected function mountShare($data) {
  399. $mount = $this->getMount($data);
  400. $this->mountManager->addMount($mount);
  401. return $mount;
  402. }
  403. /**
  404. * @return \OC\Files\Mount\Manager
  405. */
  406. public function getMountManager() {
  407. return $this->mountManager;
  408. }
  409. /**
  410. * @param string $source
  411. * @param string $target
  412. * @return bool
  413. */
  414. public function setMountPoint($source, $target) {
  415. $source = $this->stripPath($source);
  416. $target = $this->stripPath($target);
  417. $sourceHash = md5($source);
  418. $targetHash = md5($target);
  419. $query = $this->connection->prepare('
  420. UPDATE `*PREFIX*share_external`
  421. SET `mountpoint` = ?, `mountpoint_hash` = ?
  422. WHERE `mountpoint_hash` = ?
  423. AND `user` = ?
  424. ');
  425. $result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]);
  426. return $result;
  427. }
  428. public function removeShare($mountPoint) {
  429. $mountPointObj = $this->mountManager->find($mountPoint);
  430. $id = $mountPointObj->getStorage()->getCache()->getId('');
  431. $mountPoint = $this->stripPath($mountPoint);
  432. $hash = md5($mountPoint);
  433. $getShare = $this->connection->prepare('
  434. SELECT `remote`, `share_token`, `remote_id`, `share_type`, `id`
  435. FROM `*PREFIX*share_external`
  436. WHERE `mountpoint_hash` = ? AND `user` = ?');
  437. $result = $getShare->execute([$hash, $this->uid]);
  438. $share = $getShare->fetch();
  439. $getShare->closeCursor();
  440. if ($result && $share !== false && (int)$share['share_type'] === IShare::TYPE_USER) {
  441. try {
  442. $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
  443. } catch (\Throwable $e) {
  444. // if we fail to notify the remote (probably cause the remote is down)
  445. // we still want the share to be gone to prevent undeletable remotes
  446. }
  447. $query = $this->connection->prepare('
  448. DELETE FROM `*PREFIX*share_external`
  449. WHERE `id` = ?
  450. ');
  451. $result = (bool)$query->execute([(int)$share['id']]);
  452. } elseif ($result && $share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) {
  453. $query = $this->connection->prepare('
  454. UPDATE `*PREFIX*share_external`
  455. SET `accepted` = ?
  456. WHERE `id` = ?');
  457. $result = (bool)$query->execute([0, (int)$share['id']]);
  458. }
  459. if ($result) {
  460. $this->removeReShares($id);
  461. }
  462. return $result;
  463. }
  464. /**
  465. * remove re-shares from share table and mapping in the federated_reshares table
  466. *
  467. * @param $mountPointId
  468. */
  469. protected function removeReShares($mountPointId) {
  470. $selectQuery = $this->connection->getQueryBuilder();
  471. $query = $this->connection->getQueryBuilder();
  472. $selectQuery->select('id')->from('share')
  473. ->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId)));
  474. $select = $selectQuery->getSQL();
  475. $query->delete('federated_reshares')
  476. ->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')')));
  477. $query->execute();
  478. $deleteReShares = $this->connection->getQueryBuilder();
  479. $deleteReShares->delete('share')
  480. ->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId)));
  481. $deleteReShares->execute();
  482. }
  483. /**
  484. * remove all shares for user $uid if the user was deleted
  485. *
  486. * @param string $uid
  487. * @return bool
  488. */
  489. public function removeUserShares($uid) {
  490. $getShare = $this->connection->prepare('
  491. SELECT `remote`, `share_token`, `remote_id`
  492. FROM `*PREFIX*share_external`
  493. WHERE `user` = ?');
  494. $result = $getShare->execute([$uid]);
  495. if ($result) {
  496. $shares = $getShare->fetchAll();
  497. foreach ($shares as $share) {
  498. $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
  499. }
  500. }
  501. $query = $this->connection->prepare('
  502. DELETE FROM `*PREFIX*share_external`
  503. WHERE `user` = ?
  504. ');
  505. return (bool)$query->execute([$uid]);
  506. }
  507. /**
  508. * return a list of shares which are not yet accepted by the user
  509. *
  510. * @return array list of open server-to-server shares
  511. */
  512. public function getOpenShares() {
  513. return $this->getShares(false);
  514. }
  515. /**
  516. * return a list of shares which are accepted by the user
  517. *
  518. * @return array list of accepted server-to-server shares
  519. */
  520. public function getAcceptedShares() {
  521. return $this->getShares(true);
  522. }
  523. /**
  524. * return a list of shares for the user
  525. *
  526. * @param bool|null $accepted True for accepted only,
  527. * false for not accepted,
  528. * null for all shares of the user
  529. * @return array list of open server-to-server shares
  530. */
  531. private function getShares($accepted) {
  532. $user = $this->userManager->get($this->uid);
  533. $groups = $this->groupManager->getUserGroups($user);
  534. $userGroups = [];
  535. foreach ($groups as $group) {
  536. $userGroups[] = $group->getGID();
  537. }
  538. $query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
  539. FROM `*PREFIX*share_external`
  540. WHERE (`user` = ? OR `user` IN (?))';
  541. $parameters = [$this->uid, implode(',',$userGroups)];
  542. if (!is_null($accepted)) {
  543. $query .= ' AND `accepted` = ?';
  544. $parameters[] = (int) $accepted;
  545. }
  546. $query .= ' ORDER BY `id` ASC';
  547. $shares = $this->connection->prepare($query);
  548. $result = $shares->execute($parameters);
  549. return $result ? $shares->fetchAll() : [];
  550. }
  551. }