federatedShareProvider = $federatedShareProvider; $this->connection = $connection; $this->shareManager = $shareManager; $this->notifications = $notifications; $this->addressHandler = $addressHandler; $this->userManager = $userManager; $this->cloudIdManager = $cloudIdManager; $this->logger = $logger; $this->cloudFederationFactory = $cloudFederationFactory; $this->cloudFederationProviderManager = $cloudFederationProviderManager; $this->eventDispatcher = $eventDispatcher; } /** * 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, 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 Http\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 * @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 Http\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, array{}> * @throws OCSException * @throws ShareNotFound * @throws \OCP\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 Http\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, 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 Http\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, 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 Http\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, 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 Http\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 = \OCP\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, 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 Http\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 * @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 Http\DataResponse(['remote' => $cloudId->getRemote(), 'owner' => $cloudId->getUser()]); } else { throw new OCSBadRequestException('Share not found or token invalid'); } } }