RequestHandlerController.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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 Joas Schilling <coding@schilljs.com>
  9. * @author Morris Jobke <hey@morrisjobke.de>
  10. * @author Robin Appelman <robin@icewind.nl>
  11. * @author Roeland Jago Douma <roeland@famdouma.nl>
  12. *
  13. * @license AGPL-3.0
  14. *
  15. * This code is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU Affero General Public License, version 3,
  17. * as published by the Free Software Foundation.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU Affero General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public License, version 3,
  25. * along with this program. If not, see <http://www.gnu.org/licenses/>
  26. *
  27. */
  28. namespace OCA\FederatedFileSharing\Controller;
  29. use OCA\FederatedFileSharing\AddressHandler;
  30. use OCA\FederatedFileSharing\FederatedShareProvider;
  31. use OCA\FederatedFileSharing\Notifications;
  32. use OCP\AppFramework\Http;
  33. use OCP\AppFramework\OCS\OCSBadRequestException;
  34. use OCP\AppFramework\OCS\OCSException;
  35. use OCP\AppFramework\OCS\OCSForbiddenException;
  36. use OCP\AppFramework\OCSController;
  37. use OCP\App\IAppManager;
  38. use OCP\Constants;
  39. use OCP\EventDispatcher\IEventDispatcher;
  40. use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
  41. use OCP\Federation\Exceptions\ProviderDoesNotExistsException;
  42. use OCP\Federation\ICloudFederationFactory;
  43. use OCP\Federation\ICloudFederationProviderManager;
  44. use OCP\Federation\ICloudIdManager;
  45. use OCP\IDBConnection;
  46. use OCP\IRequest;
  47. use OCP\IUserManager;
  48. use OCP\Log\Audit\CriticalActionPerformedEvent;
  49. use OCP\Share;
  50. use OCP\Share\Exceptions\ShareNotFound;
  51. use Psr\Log\LoggerInterface;
  52. class RequestHandlerController extends OCSController {
  53. /** @var FederatedShareProvider */
  54. private $federatedShareProvider;
  55. /** @var IDBConnection */
  56. private $connection;
  57. /** @var Share\IManager */
  58. private $shareManager;
  59. /** @var Notifications */
  60. private $notifications;
  61. /** @var AddressHandler */
  62. private $addressHandler;
  63. /** @var IUserManager */
  64. private $userManager;
  65. /** @var string */
  66. private $shareTable = 'share';
  67. /** @var ICloudIdManager */
  68. private $cloudIdManager;
  69. /** @var LoggerInterface */
  70. private $logger;
  71. /** @var ICloudFederationFactory */
  72. private $cloudFederationFactory;
  73. /** @var ICloudFederationProviderManager */
  74. private $cloudFederationProviderManager;
  75. /** @var IEventDispatcher */
  76. private $eventDispatcher;
  77. public function __construct(string $appName,
  78. IRequest $request,
  79. FederatedShareProvider $federatedShareProvider,
  80. IDBConnection $connection,
  81. Share\IManager $shareManager,
  82. Notifications $notifications,
  83. AddressHandler $addressHandler,
  84. IUserManager $userManager,
  85. ICloudIdManager $cloudIdManager,
  86. LoggerInterface $logger,
  87. ICloudFederationFactory $cloudFederationFactory,
  88. ICloudFederationProviderManager $cloudFederationProviderManager,
  89. IEventDispatcher $eventDispatcher
  90. ) {
  91. parent::__construct($appName, $request);
  92. $this->federatedShareProvider = $federatedShareProvider;
  93. $this->connection = $connection;
  94. $this->shareManager = $shareManager;
  95. $this->notifications = $notifications;
  96. $this->addressHandler = $addressHandler;
  97. $this->userManager = $userManager;
  98. $this->cloudIdManager = $cloudIdManager;
  99. $this->logger = $logger;
  100. $this->cloudFederationFactory = $cloudFederationFactory;
  101. $this->cloudFederationProviderManager = $cloudFederationProviderManager;
  102. $this->eventDispatcher = $eventDispatcher;
  103. }
  104. /**
  105. * @NoCSRFRequired
  106. * @PublicPage
  107. *
  108. * create a new share
  109. *
  110. * @return Http\DataResponse
  111. * @throws OCSException
  112. */
  113. public function createShare() {
  114. $remote = isset($_POST['remote']) ? $_POST['remote'] : null;
  115. $token = isset($_POST['token']) ? $_POST['token'] : null;
  116. $name = isset($_POST['name']) ? $_POST['name'] : null;
  117. $owner = isset($_POST['owner']) ? $_POST['owner'] : null;
  118. $sharedBy = isset($_POST['sharedBy']) ? $_POST['sharedBy'] : null;
  119. $shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null;
  120. $remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null;
  121. $sharedByFederatedId = isset($_POST['sharedByFederatedId']) ? $_POST['sharedByFederatedId'] : null;
  122. $ownerFederatedId = isset($_POST['ownerFederatedId']) ? $_POST['ownerFederatedId'] : null;
  123. if ($ownerFederatedId === null) {
  124. $ownerFederatedId = $this->cloudIdManager->getCloudId($owner, $this->cleanupRemote($remote))->getId();
  125. }
  126. // if the owner of the share and the initiator are the same user
  127. // we also complete the federated share ID for the initiator
  128. if ($sharedByFederatedId === null && $owner === $sharedBy) {
  129. $sharedByFederatedId = $ownerFederatedId;
  130. }
  131. $share = $this->cloudFederationFactory->getCloudFederationShare(
  132. $shareWith,
  133. $name,
  134. '',
  135. $remoteId,
  136. $ownerFederatedId,
  137. $owner,
  138. $sharedByFederatedId,
  139. $sharedBy,
  140. $token,
  141. 'user',
  142. 'file'
  143. );
  144. try {
  145. $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
  146. $provider->shareReceived($share);
  147. if ($sharedByFederatedId === $ownerFederatedId) {
  148. $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('A new federated share with "%s" was created by "%s" and shared with "%s"', [$name, $ownerFederatedId, $shareWith]));
  149. } else {
  150. $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('A new federated share with "%s" was shared by "%s" (resource owner is: "%s") and shared with "%s"', [$name, $sharedByFederatedId, $ownerFederatedId, $shareWith]));
  151. }
  152. } catch (ProviderDoesNotExistsException $e) {
  153. throw new OCSException('Server does not support federated cloud sharing', 503);
  154. } catch (ProviderCouldNotAddShareException $e) {
  155. throw new OCSException($e->getMessage(), 400);
  156. } catch (\Exception $e) {
  157. throw new OCSException('internal server error, was not able to add share from ' . $remote, 500);
  158. }
  159. return new Http\DataResponse();
  160. }
  161. /**
  162. * @NoCSRFRequired
  163. * @PublicPage
  164. *
  165. * create re-share on behalf of another user
  166. *
  167. * @param int $id
  168. * @return Http\DataResponse
  169. * @throws OCSBadRequestException
  170. * @throws OCSException
  171. * @throws OCSForbiddenException
  172. */
  173. public function reShare($id) {
  174. $token = $this->request->getParam('token', null);
  175. $shareWith = $this->request->getParam('shareWith', null);
  176. $permission = (int)$this->request->getParam('permission', null);
  177. $remoteId = (int)$this->request->getParam('remoteId', null);
  178. if ($id === null ||
  179. $token === null ||
  180. $shareWith === null ||
  181. $permission === null ||
  182. $remoteId === null
  183. ) {
  184. throw new OCSBadRequestException();
  185. }
  186. $notification = [
  187. 'sharedSecret' => $token,
  188. 'shareWith' => $shareWith,
  189. 'senderId' => $remoteId,
  190. 'message' => 'Recipient of a share ask the owner to reshare the file'
  191. ];
  192. try {
  193. $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
  194. [$newToken, $localId] = $provider->notificationReceived('REQUEST_RESHARE', $id, $notification);
  195. return new Http\DataResponse([
  196. 'token' => $newToken,
  197. 'remoteId' => $localId
  198. ]);
  199. } catch (ProviderDoesNotExistsException $e) {
  200. throw new OCSException('Server does not support federated cloud sharing', 503);
  201. } catch (ShareNotFound $e) {
  202. $this->logger->debug('Share not found: ' . $e->getMessage(), ['exception' => $e]);
  203. } catch (\Exception $e) {
  204. $this->logger->debug('internal server error, can not process notification: ' . $e->getMessage(), ['exception' => $e]);
  205. }
  206. throw new OCSBadRequestException();
  207. }
  208. /**
  209. * @NoCSRFRequired
  210. * @PublicPage
  211. *
  212. * accept server-to-server share
  213. *
  214. * @param int $id
  215. * @return Http\DataResponse
  216. * @throws OCSException
  217. * @throws ShareNotFound
  218. * @throws \OCP\HintException
  219. */
  220. public function acceptShare($id) {
  221. $token = isset($_POST['token']) ? $_POST['token'] : null;
  222. $notification = [
  223. 'sharedSecret' => $token,
  224. 'message' => 'Recipient accept the share'
  225. ];
  226. try {
  227. $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
  228. $provider->notificationReceived('SHARE_ACCEPTED', $id, $notification);
  229. $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" was accepted', [$id]));
  230. } catch (ProviderDoesNotExistsException $e) {
  231. throw new OCSException('Server does not support federated cloud sharing', 503);
  232. } catch (ShareNotFound $e) {
  233. $this->logger->debug('Share not found: ' . $e->getMessage(), ['exception' => $e]);
  234. } catch (\Exception $e) {
  235. $this->logger->debug('internal server error, can not process notification: ' . $e->getMessage(), ['exception' => $e]);
  236. }
  237. return new Http\DataResponse();
  238. }
  239. /**
  240. * @NoCSRFRequired
  241. * @PublicPage
  242. *
  243. * decline server-to-server share
  244. *
  245. * @param int $id
  246. * @return Http\DataResponse
  247. * @throws OCSException
  248. */
  249. public function declineShare($id) {
  250. $token = isset($_POST['token']) ? $_POST['token'] : null;
  251. $notification = [
  252. 'sharedSecret' => $token,
  253. 'message' => 'Recipient declined the share'
  254. ];
  255. try {
  256. $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
  257. $provider->notificationReceived('SHARE_DECLINED', $id, $notification);
  258. $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" was declined', [$id]));
  259. } catch (ProviderDoesNotExistsException $e) {
  260. throw new OCSException('Server does not support federated cloud sharing', 503);
  261. } catch (ShareNotFound $e) {
  262. $this->logger->debug('Share not found: ' . $e->getMessage(), ['exception' => $e]);
  263. } catch (\Exception $e) {
  264. $this->logger->debug('internal server error, can not process notification: ' . $e->getMessage(), ['exception' => $e]);
  265. }
  266. return new Http\DataResponse();
  267. }
  268. /**
  269. * @NoCSRFRequired
  270. * @PublicPage
  271. *
  272. * remove server-to-server share if it was unshared by the owner
  273. *
  274. * @param int $id
  275. * @return Http\DataResponse
  276. * @throws OCSException
  277. */
  278. public function unshare($id) {
  279. if (!$this->isS2SEnabled()) {
  280. throw new OCSException('Server does not support federated cloud sharing', 503);
  281. }
  282. $token = isset($_POST['token']) ? $_POST['token'] : null;
  283. try {
  284. $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
  285. $notification = ['sharedSecret' => $token];
  286. $provider->notificationReceived('SHARE_UNSHARED', $id, $notification);
  287. $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" was unshared', [$id]));
  288. } catch (\Exception $e) {
  289. $this->logger->debug('processing unshare notification failed: ' . $e->getMessage(), ['exception' => $e]);
  290. }
  291. return new Http\DataResponse();
  292. }
  293. private function cleanupRemote($remote) {
  294. $remote = substr($remote, strpos($remote, '://') + 3);
  295. return rtrim($remote, '/');
  296. }
  297. /**
  298. * @NoCSRFRequired
  299. * @PublicPage
  300. *
  301. * federated share was revoked, either by the owner or the re-sharer
  302. *
  303. * @param int $id
  304. * @return Http\DataResponse
  305. * @throws OCSBadRequestException
  306. */
  307. public function revoke($id) {
  308. $token = $this->request->getParam('token');
  309. try {
  310. $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
  311. $notification = ['sharedSecret' => $token];
  312. $provider->notificationReceived('RESHARE_UNDO', $id, $notification);
  313. return new Http\DataResponse();
  314. } catch (\Exception $e) {
  315. throw new OCSBadRequestException();
  316. }
  317. }
  318. /**
  319. * check if server-to-server sharing is enabled
  320. *
  321. * @param bool $incoming
  322. * @return bool
  323. */
  324. private function isS2SEnabled($incoming = false) {
  325. $result = \OCP\Server::get(IAppManager::class)->isEnabledForUser('files_sharing');
  326. if ($incoming) {
  327. $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
  328. } else {
  329. $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
  330. }
  331. return $result;
  332. }
  333. /**
  334. * @NoCSRFRequired
  335. * @PublicPage
  336. *
  337. * update share information to keep federated re-shares in sync
  338. *
  339. * @param int $id
  340. * @return Http\DataResponse
  341. * @throws OCSBadRequestException
  342. */
  343. public function updatePermissions($id) {
  344. $token = $this->request->getParam('token', null);
  345. $ncPermissions = $this->request->getParam('permissions', null);
  346. try {
  347. $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
  348. $ocmPermissions = $this->ncPermissions2ocmPermissions((int)$ncPermissions);
  349. $notification = ['sharedSecret' => $token, 'permission' => $ocmPermissions];
  350. $provider->notificationReceived('RESHARE_CHANGE_PERMISSION', $id, $notification);
  351. $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" has updated permissions "%s"', [$id, implode(', ', $ocmPermissions)]));
  352. } catch (\Exception $e) {
  353. $this->logger->debug($e->getMessage(), ['exception' => $e]);
  354. throw new OCSBadRequestException();
  355. }
  356. return new Http\DataResponse();
  357. }
  358. /**
  359. * translate Nextcloud permissions to OCM Permissions
  360. *
  361. * @param $ncPermissions
  362. * @return array
  363. */
  364. protected function ncPermissions2ocmPermissions($ncPermissions) {
  365. $ocmPermissions = [];
  366. if ($ncPermissions & Constants::PERMISSION_SHARE) {
  367. $ocmPermissions[] = 'share';
  368. }
  369. if ($ncPermissions & Constants::PERMISSION_READ) {
  370. $ocmPermissions[] = 'read';
  371. }
  372. if (($ncPermissions & Constants::PERMISSION_CREATE) ||
  373. ($ncPermissions & Constants::PERMISSION_UPDATE)) {
  374. $ocmPermissions[] = 'write';
  375. }
  376. return $ocmPermissions;
  377. }
  378. /**
  379. * @NoCSRFRequired
  380. * @PublicPage
  381. *
  382. * change the owner of a server-to-server share
  383. *
  384. * @param int $id
  385. * @return Http\DataResponse
  386. * @throws \InvalidArgumentException
  387. * @throws OCSException
  388. */
  389. public function move($id) {
  390. if (!$this->isS2SEnabled()) {
  391. throw new OCSException('Server does not support federated cloud sharing', 503);
  392. }
  393. $token = $this->request->getParam('token');
  394. $remote = $this->request->getParam('remote');
  395. $newRemoteId = $this->request->getParam('remote_id', $id);
  396. $cloudId = $this->cloudIdManager->resolveCloudId($remote);
  397. $qb = $this->connection->getQueryBuilder();
  398. $query = $qb->update('share_external')
  399. ->set('remote', $qb->createNamedParameter($cloudId->getRemote()))
  400. ->set('owner', $qb->createNamedParameter($cloudId->getUser()))
  401. ->set('remote_id', $qb->createNamedParameter($newRemoteId))
  402. ->where($qb->expr()->eq('remote_id', $qb->createNamedParameter($id)))
  403. ->andWhere($qb->expr()->eq('share_token', $qb->createNamedParameter($token)));
  404. $affected = $query->executeStatement();
  405. if ($affected > 0) {
  406. return new Http\DataResponse(['remote' => $cloudId->getRemote(), 'owner' => $cloudId->getUser()]);
  407. } else {
  408. throw new OCSBadRequestException('Share not found or token invalid');
  409. }
  410. }
  411. }