1<?php 2 3declare(strict_types=1); 4/** 5 * @copyright Copyright (c) 2021 Gary Kim <gary@garykim.dev> 6 * 7 * @author Gary Kim <gary@garykim.dev> 8 * 9 * @license GNU AGPL version 3 or any later version 10 * 11 * This program is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Affero General Public License as 13 * published by the Free Software Foundation, either version 3 of the 14 * License, or (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU Affero General Public License for more details. 20 * 21 * You should have received a copy of the GNU Affero General Public License 22 * along with this program. If not, see <http://www.gnu.org/licenses/>. 23 * 24 */ 25 26namespace OCA\Talk\Federation; 27 28use OCA\Talk\AppInfo\Application; 29use OCA\Talk\Exceptions\CannotReachRemoteException; 30use OCA\Talk\Exceptions\RoomNotFoundException; 31use OCA\Talk\Exceptions\UnauthorizedException; 32use OCA\Talk\Manager; 33use OCA\Talk\Model\Attendee; 34use OCA\Talk\Model\Invitation; 35use OCA\Talk\Model\InvitationMapper; 36use OCA\Talk\Room; 37use OCA\Talk\Service\ParticipantService; 38use OCP\AppFramework\Db\DoesNotExistException; 39use OCP\AppFramework\Db\MultipleObjectsReturnedException; 40use OCP\DB\Exception as DBException; 41use OCP\IConfig; 42use OCP\IUser; 43 44/** 45 * Class FederationManager 46 * 47 * @package OCA\Talk\Federation 48 * 49 * FederationManager handles incoming federated rooms 50 */ 51class FederationManager { 52 public const TALK_ROOM_RESOURCE = 'talk-room'; 53 public const TALK_PROTOCOL_NAME = 'nctalk'; 54 public const TOKEN_LENGTH = 15; 55 56 /** @var IConfig */ 57 private $config; 58 59 /** @var Manager */ 60 private $manager; 61 62 /** @var ParticipantService */ 63 private $participantService; 64 65 /** @var InvitationMapper */ 66 private $invitationMapper; 67 68 /** @var Notifications */ 69 private $notifications; 70 71 public function __construct( 72 IConfig $config, 73 Manager $manager, 74 ParticipantService $participantService, 75 InvitationMapper $invitationMapper, 76 Notifications $notifications 77 ) { 78 $this->config = $config; 79 $this->manager = $manager; 80 $this->participantService = $participantService; 81 $this->invitationMapper = $invitationMapper; 82 $this->notifications = $notifications; 83 } 84 85 /** 86 * Determine if Talk federation is enabled on this instance 87 * @return bool 88 * @deprecated use \OCA\Talk\Config::isFederationEnabled() 89 */ 90 public function isEnabled(): bool { 91 // TODO: Set to default true once implementation is complete 92 return $this->config->getAppValue(Application::APP_ID, 'federation_enabled', 'no') === 'yes'; 93 } 94 95 /** 96 * @param IUser $user 97 * @param string $remoteId 98 * @param int $roomType 99 * @param string $roomName 100 * @param string $roomToken 101 * @param string $remoteUrl 102 * @param string $sharedSecret 103 * @return int share id for this specific remote room share 104 * @throws DBException 105 */ 106 public function addRemoteRoom(IUser $user, string $remoteId, int $roomType, string $roomName, string $roomToken, string $remoteUrl, string $sharedSecret): int { 107 try { 108 $room = $this->manager->getRoomByToken($roomToken, null, $remoteUrl); 109 } catch (RoomNotFoundException $ex) { 110 $room = $this->manager->createRemoteRoom($roomType, $roomName, $roomToken, $remoteUrl); 111 } 112 $invitation = new Invitation(); 113 $invitation->setUserId($user->getUID()); 114 $invitation->setRoomId($room->getId()); 115 $invitation->setAccessToken($sharedSecret); 116 $invitation->setRemoteId($remoteId); 117 $invitation = $this->invitationMapper->insert($invitation); 118 119 return $invitation->getId(); 120 } 121 122 /** 123 * @throws DBException 124 * @throws UnauthorizedException 125 * @throws MultipleObjectsReturnedException 126 * @throws DoesNotExistException 127 * @throws CannotReachRemoteException 128 */ 129 public function acceptRemoteRoomShare(IUser $user, int $shareId): void { 130 $invitation = $this->invitationMapper->getInvitationById($shareId); 131 if ($invitation->getUserId() !== $user->getUID()) { 132 throw new UnauthorizedException('invitation is for a different user'); 133 } 134 135 // Add user to the room 136 $room = $this->manager->getRoomById($invitation->getRoomId()); 137 if ( 138 !$this->notifications->sendShareAccepted($room->getServerUrl(), $invitation->getRemoteId(), $invitation->getAccessToken()) 139 ) { 140 throw new CannotReachRemoteException(); 141 } 142 143 144 $participant = [ 145 [ 146 'actorType' => Attendee::ACTOR_USERS, 147 'actorId' => $user->getUID(), 148 'displayName' => $user->getDisplayName(), 149 'accessToken' => $invitation->getAccessToken(), 150 'remoteId' => $invitation->getRemoteId(), 151 ] 152 ]; 153 $this->participantService->addUsers($room, $participant, $user); 154 155 $this->invitationMapper->delete($invitation); 156 } 157 158 /** 159 * @throws DBException 160 * @throws UnauthorizedException 161 * @throws MultipleObjectsReturnedException 162 * @throws DoesNotExistException 163 */ 164 public function rejectRemoteRoomShare(IUser $user, int $shareId): void { 165 $invitation = $this->invitationMapper->getInvitationById($shareId); 166 if ($invitation->getUserId() !== $user->getUID()) { 167 throw new UnauthorizedException('invitation is for a different user'); 168 } 169 170 $room = $this->manager->getRoomById($invitation->getRoomId()); 171 172 $this->invitationMapper->delete($invitation); 173 174 $this->notifications->sendShareDeclined($room->getServerUrl(), $invitation->getRemoteId(), $invitation->getAccessToken()); 175 } 176 177 /** 178 * @throws DBException 179 */ 180 public function getNumberOfInvitations(Room $room): int { 181 return $this->invitationMapper->countInvitationsForRoom($room); 182 } 183} 184