123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- <?php
- /**
- * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
- * SPDX-License-Identifier: AGPL-3.0-only
- */
- namespace OCA\FederatedFileSharing\Controller;
- use OCA\FederatedFileSharing\AddressHandler;
- use OCA\FederatedFileSharing\FederatedShareProvider;
- use OCA\FederatedFileSharing\Notifications;
- use OCP\App\IAppManager;
- use OCP\AppFramework\Http;
- use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
- use OCP\AppFramework\Http\Attribute\OpenAPI;
- use OCP\AppFramework\Http\Attribute\PublicPage;
- use OCP\AppFramework\Http\DataResponse;
- use OCP\AppFramework\OCS\OCSBadRequestException;
- use OCP\AppFramework\OCS\OCSException;
- use OCP\AppFramework\OCSController;
- use OCP\Constants;
- use OCP\EventDispatcher\IEventDispatcher;
- use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
- use OCP\Federation\Exceptions\ProviderDoesNotExistsException;
- use OCP\Federation\ICloudFederationFactory;
- use OCP\Federation\ICloudFederationProviderManager;
- use OCP\Federation\ICloudIdManager;
- use OCP\HintException;
- use OCP\IDBConnection;
- use OCP\IRequest;
- use OCP\IUserManager;
- use OCP\Log\Audit\CriticalActionPerformedEvent;
- use OCP\Server;
- use OCP\Share;
- use OCP\Share\Exceptions\ShareNotFound;
- use Psr\Log\LoggerInterface;
- #[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)]
- class RequestHandlerController extends OCSController {
- /** @var string */
- private $shareTable = 'share';
- public function __construct(
- string $appName,
- IRequest $request,
- private FederatedShareProvider $federatedShareProvider,
- private IDBConnection $connection,
- private Share\IManager $shareManager,
- private Notifications $notifications,
- private AddressHandler $addressHandler,
- private IUserManager $userManager,
- private ICloudIdManager $cloudIdManager,
- private LoggerInterface $logger,
- private ICloudFederationFactory $cloudFederationFactory,
- private ICloudFederationProviderManager $cloudFederationProviderManager,
- private IEventDispatcher $eventDispatcher,
- ) {
- parent::__construct($appName, $request);
- }
- /**
- * create a new share
- *
- * @param string|null $remote Address of the remote
- * @param string|null $token Shared secret between servers
- * @param string|null $name Name of the shared resource
- * @param string|null $owner Display name of the receiver
- * @param string|null $sharedBy Display name of the sender
- * @param string|null $shareWith ID of the user that receives the share
- * @param int|null $remoteId ID of the remote
- * @param string|null $sharedByFederatedId Federated ID of the sender
- * @param string|null $ownerFederatedId Federated ID of the receiver
- * @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
- * @throws OCSException
- *
- * 200: Share created successfully
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function createShare(
- ?string $remote = null,
- ?string $token = null,
- ?string $name = null,
- ?string $owner = null,
- ?string $sharedBy = null,
- ?string $shareWith = null,
- ?int $remoteId = null,
- ?string $sharedByFederatedId = null,
- ?string $ownerFederatedId = null,
- ) {
- if ($ownerFederatedId === null) {
- $ownerFederatedId = $this->cloudIdManager->getCloudId($owner, $this->cleanupRemote($remote))->getId();
- }
- // if the owner of the share and the initiator are the same user
- // we also complete the federated share ID for the initiator
- if ($sharedByFederatedId === null && $owner === $sharedBy) {
- $sharedByFederatedId = $ownerFederatedId;
- }
- $share = $this->cloudFederationFactory->getCloudFederationShare(
- $shareWith,
- $name,
- '',
- $remoteId,
- $ownerFederatedId,
- $owner,
- $sharedByFederatedId,
- $sharedBy,
- $token,
- 'user',
- 'file'
- );
- try {
- $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
- $provider->shareReceived($share);
- if ($sharedByFederatedId === $ownerFederatedId) {
- $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('A new federated share with "%s" was created by "%s" and shared with "%s"', [$name, $ownerFederatedId, $shareWith]));
- } else {
- $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]));
- }
- } catch (ProviderDoesNotExistsException $e) {
- throw new OCSException('Server does not support federated cloud sharing', 503);
- } catch (ProviderCouldNotAddShareException $e) {
- throw new OCSException($e->getMessage(), 400);
- } catch (\Exception $e) {
- throw new OCSException('internal server error, was not able to add share from ' . $remote, 500);
- }
- return new DataResponse();
- }
- /**
- * create re-share on behalf of another user
- *
- * @param int $id ID of the share
- * @param string|null $token Shared secret between servers
- * @param string|null $shareWith ID of the user that receives the share
- * @param int|null $remoteId ID of the remote
- * @return Http\DataResponse<Http::STATUS_OK, array{token: string, remoteId: string}, array{}>
- * @throws OCSBadRequestException Re-sharing is not possible
- * @throws OCSException
- *
- * 200: Remote share returned
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function reShare(int $id, ?string $token = null, ?string $shareWith = null, ?int $remoteId = 0) {
- if ($token === null ||
- $shareWith === null ||
- $remoteId === null
- ) {
- throw new OCSBadRequestException();
- }
- $notification = [
- 'sharedSecret' => $token,
- 'shareWith' => $shareWith,
- 'senderId' => $remoteId,
- 'message' => 'Recipient of a share ask the owner to reshare the file'
- ];
- try {
- $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
- [$newToken, $localId] = $provider->notificationReceived('REQUEST_RESHARE', $id, $notification);
- return new DataResponse([
- 'token' => $newToken,
- 'remoteId' => $localId
- ]);
- } catch (ProviderDoesNotExistsException $e) {
- throw new OCSException('Server does not support federated cloud sharing', 503);
- } catch (ShareNotFound $e) {
- $this->logger->debug('Share not found: ' . $e->getMessage(), ['exception' => $e]);
- } catch (\Exception $e) {
- $this->logger->debug('internal server error, can not process notification: ' . $e->getMessage(), ['exception' => $e]);
- }
- throw new OCSBadRequestException();
- }
- /**
- * accept server-to-server share
- *
- * @param int $id ID of the remote share
- * @param string|null $token Shared secret between servers
- * @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
- * @throws OCSException
- * @throws ShareNotFound
- * @throws HintException
- *
- * 200: Share accepted successfully
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function acceptShare(int $id, ?string $token = null) {
- $notification = [
- 'sharedSecret' => $token,
- 'message' => 'Recipient accept the share'
- ];
- try {
- $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
- $provider->notificationReceived('SHARE_ACCEPTED', $id, $notification);
- $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" was accepted', [$id]));
- } catch (ProviderDoesNotExistsException $e) {
- throw new OCSException('Server does not support federated cloud sharing', 503);
- } catch (ShareNotFound $e) {
- $this->logger->debug('Share not found: ' . $e->getMessage(), ['exception' => $e]);
- } catch (\Exception $e) {
- $this->logger->debug('internal server error, can not process notification: ' . $e->getMessage(), ['exception' => $e]);
- }
- return new DataResponse();
- }
- /**
- * decline server-to-server share
- *
- * @param int $id ID of the remote share
- * @param string|null $token Shared secret between servers
- * @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
- * @throws OCSException
- *
- * 200: Share declined successfully
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function declineShare(int $id, ?string $token = null) {
- $notification = [
- 'sharedSecret' => $token,
- 'message' => 'Recipient declined the share'
- ];
- try {
- $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
- $provider->notificationReceived('SHARE_DECLINED', $id, $notification);
- $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" was declined', [$id]));
- } catch (ProviderDoesNotExistsException $e) {
- throw new OCSException('Server does not support federated cloud sharing', 503);
- } catch (ShareNotFound $e) {
- $this->logger->debug('Share not found: ' . $e->getMessage(), ['exception' => $e]);
- } catch (\Exception $e) {
- $this->logger->debug('internal server error, can not process notification: ' . $e->getMessage(), ['exception' => $e]);
- }
- return new DataResponse();
- }
- /**
- * remove server-to-server share if it was unshared by the owner
- *
- * @param int $id ID of the share
- * @param string|null $token Shared secret between servers
- * @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
- * @throws OCSException
- *
- * 200: Share unshared successfully
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function unshare(int $id, ?string $token = null) {
- if (!$this->isS2SEnabled()) {
- throw new OCSException('Server does not support federated cloud sharing', 503);
- }
- try {
- $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
- $notification = ['sharedSecret' => $token];
- $provider->notificationReceived('SHARE_UNSHARED', $id, $notification);
- $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" was unshared', [$id]));
- } catch (\Exception $e) {
- $this->logger->debug('processing unshare notification failed: ' . $e->getMessage(), ['exception' => $e]);
- }
- return new DataResponse();
- }
- private function cleanupRemote($remote) {
- $remote = substr($remote, strpos($remote, '://') + 3);
- return rtrim($remote, '/');
- }
- /**
- * federated share was revoked, either by the owner or the re-sharer
- *
- * @param int $id ID of the share
- * @param string|null $token Shared secret between servers
- * @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
- * @throws OCSBadRequestException Revoking the share is not possible
- *
- * 200: Share revoked successfully
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function revoke(int $id, ?string $token = null) {
- try {
- $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
- $notification = ['sharedSecret' => $token];
- $provider->notificationReceived('RESHARE_UNDO', $id, $notification);
- return new DataResponse();
- } catch (\Exception $e) {
- throw new OCSBadRequestException();
- }
- }
- /**
- * check if server-to-server sharing is enabled
- *
- * @param bool $incoming
- * @return bool
- */
- private function isS2SEnabled($incoming = false) {
- $result = Server::get(IAppManager::class)->isEnabledForUser('files_sharing');
- if ($incoming) {
- $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
- } else {
- $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
- }
- return $result;
- }
- /**
- * update share information to keep federated re-shares in sync
- *
- * @param int $id ID of the share
- * @param string|null $token Shared secret between servers
- * @param int|null $permissions New permissions
- * @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
- * @throws OCSBadRequestException Updating permissions is not possible
- *
- * 200: Permissions updated successfully
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function updatePermissions(int $id, ?string $token = null, ?int $permissions = null) {
- $ncPermissions = $permissions;
- try {
- $provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
- $ocmPermissions = $this->ncPermissions2ocmPermissions((int)$ncPermissions);
- $notification = ['sharedSecret' => $token, 'permission' => $ocmPermissions];
- $provider->notificationReceived('RESHARE_CHANGE_PERMISSION', $id, $notification);
- $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('Federated share with id "%s" has updated permissions "%s"', [$id, implode(', ', $ocmPermissions)]));
- } catch (\Exception $e) {
- $this->logger->debug($e->getMessage(), ['exception' => $e]);
- throw new OCSBadRequestException();
- }
- return new DataResponse();
- }
- /**
- * translate Nextcloud permissions to OCM Permissions
- *
- * @param $ncPermissions
- * @return array
- */
- protected function ncPermissions2ocmPermissions($ncPermissions) {
- $ocmPermissions = [];
- if ($ncPermissions & Constants::PERMISSION_SHARE) {
- $ocmPermissions[] = 'share';
- }
- if ($ncPermissions & Constants::PERMISSION_READ) {
- $ocmPermissions[] = 'read';
- }
- if (($ncPermissions & Constants::PERMISSION_CREATE) ||
- ($ncPermissions & Constants::PERMISSION_UPDATE)) {
- $ocmPermissions[] = 'write';
- }
- return $ocmPermissions;
- }
- /**
- * change the owner of a server-to-server share
- *
- * @param int $id ID of the share
- * @param string|null $token Shared secret between servers
- * @param string|null $remote Address of the remote
- * @param string|null $remote_id ID of the remote
- * @return Http\DataResponse<Http::STATUS_OK, array{remote: string, owner: string}, array{}>
- * @throws OCSBadRequestException Moving share is not possible
- *
- * 200: Share moved successfully
- */
- #[NoCSRFRequired]
- #[PublicPage]
- public function move(int $id, ?string $token = null, ?string $remote = null, ?string $remote_id = null) {
- if (!$this->isS2SEnabled()) {
- throw new OCSException('Server does not support federated cloud sharing', 503);
- }
- $newRemoteId = (string)($remote_id ?? $id);
- $cloudId = $this->cloudIdManager->resolveCloudId($remote);
- $qb = $this->connection->getQueryBuilder();
- $query = $qb->update('share_external')
- ->set('remote', $qb->createNamedParameter($cloudId->getRemote()))
- ->set('owner', $qb->createNamedParameter($cloudId->getUser()))
- ->set('remote_id', $qb->createNamedParameter($newRemoteId))
- ->where($qb->expr()->eq('remote_id', $qb->createNamedParameter($id)))
- ->andWhere($qb->expr()->eq('share_token', $qb->createNamedParameter($token)));
- $affected = $query->executeStatement();
- if ($affected > 0) {
- return new DataResponse(['remote' => $cloudId->getRemote(), 'owner' => $cloudId->getUser()]);
- } else {
- throw new OCSBadRequestException('Share not found or token invalid');
- }
- }
- }
|