diff options
author | Gary Kim <gary@garykim.dev> | 2021-06-10 23:35:54 -0400 |
---|---|---|
committer | Gary Kim <gary@garykim.dev> | 2021-07-15 13:54:35 -0400 |
commit | a35b98f8c15e19bde6098081b41e544e95e4b532 (patch) | |
tree | a9af9784cd16ae1c4321c8c29e1ab8996db57196 /lib/Federation/CloudFederationProviderTalk.php | |
parent | ddda9be34bd14f8ccf14521a6e100870493cd4b3 (diff) |
Implement CloudFederationProvider for Talk
Signed-off-by: Gary Kim <gary@garykim.dev>
Diffstat (limited to 'lib/Federation/CloudFederationProviderTalk.php')
-rw-r--r-- | lib/Federation/CloudFederationProviderTalk.php | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php new file mode 100644 index 000000000..9cf401afd --- /dev/null +++ b/lib/Federation/CloudFederationProviderTalk.php @@ -0,0 +1,262 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2021 Gary Kim <gary@garykim.dev> + * + * @author Gary Kim <gary@garykim.dev> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Federation; + +use Exception; +use OC\AppFramework\Http; +use OC\HintException; +use OCA\FederatedFileSharing\AddressHandler; +use OCA\Talk\AppInfo\Application; +use OCA\Talk\Manager; +use OCA\Talk\Model\Attendee; +use OCA\Talk\Model\AttendeeMapper; +use OCA\Talk\Participant; +use OCA\Talk\Service\ParticipantService; +use OCP\DB\Exception as DBException; +use OCP\Federation\Exceptions\ActionNotSupportedException; +use OCP\Federation\Exceptions\AuthenticationFailedException; +use OCP\Federation\Exceptions\BadRequestException; +use OCP\Federation\Exceptions\ProviderCouldNotAddShareException; +use OCP\Federation\ICloudFederationProvider; +use OCP\Federation\ICloudFederationShare; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Notification\IManager as INotificationManager; +use OCP\Share\Exceptions\ShareNotFound; + +class CloudFederationProviderTalk implements ICloudFederationProvider { + + /** @var IUserManager */ + private $userManager; + + /** @var AddressHandler */ + private $addressHandler; + + /** @var FederationManager */ + private $federationManager; + + /** @var INotificationManager */ + private $notificationManager; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var ParticipantService */ + private $participantService; + + /** @var AttendeeMapper */ + private $attendeeMapper; + + /** @var Manager */ + private $manager; + + public function __construct( + IUserManager $userManager, + AddressHandler $addressHandler, + FederationManager $federationManager, + INotificationManager $notificationManager, + IURLGenerator $urlGenerator, + ParticipantService $participantService, + AttendeeMapper $attendeeMapper, + Manager $manager + ) { + $this->userManager = $userManager; + $this->addressHandler = $addressHandler; + $this->federationManager = $federationManager; + $this->notificationManager = $notificationManager; + $this->urlGenerator = $urlGenerator; + $this->participantService = $participantService; + $this->attendeeMapper = $attendeeMapper; + $this->manager = $manager; + } + + /** + * @inheritDoc + */ + public function getShareType(): string { + return 'talk-room'; + } + + /** + * @inheritDoc + * @throws HintException + * @throws DBException + */ + public function shareReceived(ICloudFederationShare $share): string { + if (!$this->federationManager->isEnabled()) { + throw new ProviderCouldNotAddShareException('Server does not support talk federation', '', Http::STATUS_SERVICE_UNAVAILABLE); + } + if ($share->getShareType() !== 'user') { + throw new ProviderCouldNotAddShareException('support for sharing with non-users not implemented yet', '', Http::STATUS_NOT_IMPLEMENTED); + // TODO: Implement group shares + } + + if (!is_numeric($share->getShareType())) { + throw new ProviderCouldNotAddShareException('RoomType is not a number', '', Http::STATUS_BAD_REQUEST); + } + + $shareSecret = $share->getShareSecret(); + $shareWith = $share->getShareWith(); + $roomToken = $share->getProviderId(); + $roomName = $share->getResourceName(); + $roomType = (int) $share->getShareType(); + $sharedBy = $share->getSharedByDisplayName(); + $sharedByFederatedId = $share->getSharedBy(); + $owner = $share->getOwnerDisplayName(); + $ownerFederatedId = $share->getOwner(); + [, $remote] = $this->addressHandler->splitUserRemote($ownerFederatedId); + + // if no explicit information about the person who created the share was send + // we assume that the share comes from the owner + if ($sharedByFederatedId === null) { + $sharedBy = $owner; + $sharedByFederatedId = $ownerFederatedId; + } + + if ($remote && $shareSecret && $shareWith && $roomToken && $roomName && $owner) { + $shareWith = $this->userManager->get($shareWith); + if ($shareWith === null) { + throw new ProviderCouldNotAddShareException('User does not exist', '',Http::STATUS_BAD_REQUEST); + } + + $shareId = (string) $this->federationManager->addRemoteRoom($shareWith, $roomType, $roomName, $roomToken, $remote, $shareSecret); + + $this->notifyAboutNewShare($shareWith, $shareId, $sharedByFederatedId, $sharedBy, $roomName, $roomToken, $remote); + return $shareId; + } + throw new ProviderCouldNotAddShareException('required request data not found', '', Http::STATUS_BAD_REQUEST); + } + + /** + * @inheritDoc + */ + public function notificationReceived($notificationType, $providerId, array $notification): array { + if (!is_numeric($providerId)) { + throw new BadRequestException(['providerId']); + } + switch ($notificationType) { + case 'SHARE_ACCEPTED': + return $this->shareAccepted((int) $providerId, $notification); + case 'SHARE_DECLINED': + return $this->shareDeclined((int) $providerId, $notification); + case 'SHARE_UNSHARED': + return []; // TODO: Implement + case 'REQUEST_RESHARE': + return []; // TODO: Implement + case 'RESHARE_UNDO': + return []; // TODO: Implement + case 'RESHARE_CHANGE_PERMISSION': + return []; // TODO: Implement + } + // TODO: Implement notificationReceived() method. + } + + /** + * @throws ActionNotSupportedException + * @throws ShareNotFound + * @throws AuthenticationFailedException + */ + private function shareAccepted(int $id, array $notification): array { + $attendee = $this->getAttendeeAndValidate($id, $notification['sharedSecret']); + + // TODO: Add activity for share accepted + + return []; + } + + /** + * @throws ActionNotSupportedException + * @throws ShareNotFound + * @throws AuthenticationFailedException + */ + private function shareDeclined(int $id, array $notification): array { + $attendee = $this->getAttendeeAndValidate($id, $notification['sharedSecret']); + + $room = $this->manager->getRoomById($attendee->getRoomId()); + $participant = new Participant($room, $attendee, null); + $this->participantService->removeAttendee($room, $participant, 'Left Room'); + return []; + } + + /** + * @throws AuthenticationFailedException + * @throws ActionNotSupportedException + * @throws ShareNotFound + */ + private function getAttendeeAndValidate(int $id, string $sharedSecret): Attendee { + if (!$this->federationManager->isEnabled()) { + throw new ActionNotSupportedException('Server does not support Talk federation'); + } + + try { + $attendee = $this->attendeeMapper->getById($id); + } catch (Exception $ex) { + throw new ShareNotFound(); + } + if ($attendee->getActorType() !== Attendee::ACTOR_FEDERATED_REMOTE_USER) { + throw new ShareNotFound(); + } + if ($attendee->getAccessToken() !== $sharedSecret) { + throw new AuthenticationFailedException(); + } + return $attendee; + } + + private function notifyAboutNewShare(IUser $shareWith, string $shareId, string $sharedByFederatedId, string $sharedByName, string $roomName, string $roomToken, string $serverUrl) { + $notification = $this->notificationManager->createNotification(); + $notification->setApp(Application::APP_ID) + ->setUser($shareWith->getUID()) + ->setDateTime(new \DateTime()) + ->setObject('remote_talk_share', $shareId) + ->setSubject('remote_talk_share', [ + 'sharedByDisplayName' => $sharedByName, + 'sharedByFederatedId' => $sharedByFederatedId, + 'roomName' => $roomName, + 'serverUrl' => $serverUrl, + 'roomToken' => $roomToken, + ]); + + $declineAction = $notification->createAction(); + $declineAction->setLabel('decline') + ->setLink($this->urlGenerator->linkToOCSRouteAbsolute('spreed.Federation.rejectShare', ['id' => $shareId]), 'DELETE'); + $notification->addAction($declineAction); + + $acceptAction = $notification->createAction(); + $acceptAction->setLabel('accept') + ->setLink($this->urlGenerator->linkToOCSRouteAbsolute('spreed.Federation.acceptShare', ['id' => $shareId]), 'POST'); + $notification->addAction($acceptAction); + + $this->notificationManager->notify($notification); + } + + /** + * @inheritDoc + */ + public function getSupportedShareTypes() { + return ['user']; + } +} |