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