1<?php
2
3declare(strict_types=1);
4/**
5 * @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
6 *
7 * @license GNU AGPL version 3 or any later version
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as
11 * published by the Free Software Foundation, either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 *
22 */
23
24namespace OCA\Talk\Middleware;
25
26use OCA\Talk\Controller\AEnvironmentAwareController;
27use OCA\Talk\Exceptions\ParticipantNotFoundException;
28use OCA\Talk\Exceptions\RoomNotFoundException;
29use OCA\Talk\Manager;
30use OCA\Talk\Middleware\Exceptions\LobbyException;
31use OCA\Talk\Middleware\Exceptions\NotAModeratorException;
32use OCA\Talk\Middleware\Exceptions\ReadOnlyException;
33use OCA\Talk\Model\Attendee;
34use OCA\Talk\Participant;
35use OCA\Talk\Room;
36use OCA\Talk\TalkSession;
37use OCA\Talk\Webinary;
38use OCP\AppFramework\Controller;
39use OCP\AppFramework\Http;
40use OCP\AppFramework\Http\Response;
41use OCP\AppFramework\Http\RedirectToDefaultAppResponse;
42use OCP\AppFramework\Middleware;
43use OCP\AppFramework\OCS\OCSException;
44use OCP\AppFramework\OCSController;
45use OCP\AppFramework\Utility\IControllerMethodReflector;
46use OCP\IRequest;
47
48class InjectionMiddleware extends Middleware {
49
50	/** @var IRequest */
51	private $request;
52	/** @var IControllerMethodReflector */
53	private $reflector;
54	/** @var TalkSession */
55	private $talkSession;
56	/** @var Manager */
57	private $manager;
58	/** @var ?string */
59	private $userId;
60
61	public function __construct(IRequest $request,
62								IControllerMethodReflector $reflector,
63								TalkSession $talkSession,
64								Manager $manager,
65								?string $userId) {
66		$this->request = $request;
67		$this->reflector = $reflector;
68		$this->talkSession = $talkSession;
69		$this->manager = $manager;
70		$this->userId = $userId;
71	}
72
73	/**
74	 * @param Controller $controller
75	 * @param string $methodName
76	 * @throws RoomNotFoundException
77	 * @throws ParticipantNotFoundException
78	 * @throws NotAModeratorException
79	 * @throws ReadOnlyException
80	 * @throws LobbyException
81	 */
82	public function beforeController($controller, $methodName): void {
83		if (!$controller instanceof AEnvironmentAwareController) {
84			return;
85		}
86
87		$apiVersion = $this->request->getParam('apiVersion');
88		$controller->setAPIVersion((int) substr($apiVersion, 1));
89
90		if ($this->reflector->hasAnnotation('RequireLoggedInParticipant')) {
91			$this->getLoggedIn($controller, false);
92		}
93
94		if ($this->reflector->hasAnnotation('RequireLoggedInModeratorParticipant')) {
95			$this->getLoggedIn($controller, true);
96		}
97
98		if ($this->reflector->hasAnnotation('RequireParticipant')) {
99			$this->getLoggedInOrGuest($controller, false);
100		}
101
102		if ($this->reflector->hasAnnotation('RequireModeratorParticipant')) {
103			$this->getLoggedInOrGuest($controller, true);
104		}
105
106		if ($this->reflector->hasAnnotation('RequireRoom')) {
107			$this->getRoom($controller);
108		}
109
110		if ($this->reflector->hasAnnotation('RequireReadWriteConversation')) {
111			$this->checkReadOnlyState($controller);
112		}
113
114		if ($this->reflector->hasAnnotation('RequireModeratorOrNoLobby')) {
115			$this->checkLobbyState($controller);
116		}
117	}
118
119	/**
120	 * @param AEnvironmentAwareController $controller
121	 */
122	protected function getRoom(AEnvironmentAwareController $controller): void {
123		$token = $this->request->getParam('token');
124		$room = $this->manager->getRoomByToken($token);
125		$controller->setRoom($room);
126	}
127
128	/**
129	 * @param AEnvironmentAwareController $controller
130	 * @param bool $moderatorRequired
131	 * @throws NotAModeratorException
132	 */
133	protected function getLoggedIn(AEnvironmentAwareController $controller, bool $moderatorRequired): void {
134		$token = $this->request->getParam('token');
135		$sessionId = $this->talkSession->getSessionForRoom($token);
136		$room = $this->manager->getRoomForUserByToken($token, $this->userId, $sessionId);
137		$controller->setRoom($room);
138
139		$participant = $room->getParticipant($this->userId, $sessionId);
140		$controller->setParticipant($participant);
141
142		if ($moderatorRequired && !$participant->hasModeratorPermissions(false)) {
143			throw new NotAModeratorException();
144		}
145	}
146
147	/**
148	 * @param AEnvironmentAwareController $controller
149	 * @param bool $moderatorRequired
150	 * @throws NotAModeratorException
151	 * @throws ParticipantNotFoundException
152	 */
153	protected function getLoggedInOrGuest(AEnvironmentAwareController $controller, bool $moderatorRequired): void {
154		$token = $this->request->getParam('token');
155		$sessionId = $this->talkSession->getSessionForRoom($token);
156		$room = $this->manager->getRoomForUserByToken($token, $this->userId, $sessionId);
157		$controller->setRoom($room);
158		$participant = null;
159
160		if ($sessionId !== null) {
161			try {
162				$participant = $room->getParticipantBySession($sessionId);
163			} catch (ParticipantNotFoundException $e) {
164				// ignore and fall back in case a concurrent request might have
165				// invalidated the session
166			}
167		}
168
169		if ($participant === null) {
170			$participant = $room->getParticipant($this->userId);
171		}
172
173		$controller->setParticipant($participant);
174
175		if ($moderatorRequired && !$participant->hasModeratorPermissions()) {
176			throw new NotAModeratorException();
177		}
178	}
179
180	/**
181	 * @param AEnvironmentAwareController $controller
182	 * @throws ReadOnlyException
183	 */
184	protected function checkReadOnlyState(AEnvironmentAwareController $controller): void {
185		$room = $controller->getRoom();
186		if (!$room instanceof Room || $room->getReadOnly() === Room::READ_ONLY) {
187			throw new ReadOnlyException();
188		}
189	}
190
191	/**
192	 * @param AEnvironmentAwareController $controller
193	 * @throws LobbyException
194	 */
195	protected function checkLobbyState(AEnvironmentAwareController $controller): void {
196		try {
197			$this->getLoggedInOrGuest($controller, true);
198			return;
199		} catch (NotAModeratorException $e) {
200		} catch (ParticipantNotFoundException $e) {
201		}
202
203		$participant = $controller->getParticipant();
204		if ($participant instanceof Participant &&
205			$participant->getPermissions() & Attendee::PERMISSIONS_LOBBY_IGNORE) {
206			return;
207		}
208
209		$room = $controller->getRoom();
210		if (!$room instanceof Room || $room->getLobbyState() !== Webinary::LOBBY_NONE) {
211			throw new LobbyException();
212		}
213	}
214
215	/**
216	 * @param Controller $controller
217	 * @param string $methodName
218	 * @param \Exception $exception
219	 * @throws \Exception
220	 * @return Response
221	 */
222	public function afterException($controller, $methodName, \Exception $exception): Response {
223		if ($exception instanceof RoomNotFoundException ||
224			$exception instanceof ParticipantNotFoundException) {
225			if ($controller instanceof OCSController) {
226				throw new OCSException('', Http::STATUS_NOT_FOUND);
227			}
228
229			return new RedirectToDefaultAppResponse();
230		}
231
232		if ($exception instanceof LobbyException) {
233			if ($controller instanceof OCSController) {
234				throw new OCSException('', Http::STATUS_PRECONDITION_FAILED);
235			}
236
237			return new RedirectToDefaultAppResponse();
238		}
239
240		if ($exception instanceof NotAModeratorException ||
241			$exception instanceof ReadOnlyException) {
242			if ($controller instanceof OCSController) {
243				throw new OCSException('', Http::STATUS_FORBIDDEN);
244			}
245
246			return new RedirectToDefaultAppResponse();
247		}
248
249		throw $exception;
250	}
251}
252