request->getParam('apiVersion'); $controller->setAPIVersion((int)substr($apiVersion, 1)); if (!empty($reflectionMethod->getAttributes(AllowWithoutParticipantWhenPendingInvitation::class))) { try { $this->getRoomByInvite($controller); return; } catch (RoomNotFoundException|ParticipantNotFoundException) { // Falling back to bellow checks } } if (!empty($reflectionMethod->getAttributes(RequireAuthenticatedParticipant::class))) { $this->getLoggedInOrGuest($controller, false, requireFederationWhenNotLoggedIn: true); } if (!empty($reflectionMethod->getAttributes(RequireLoggedInParticipant::class))) { $this->getLoggedIn($controller, false); } if (!empty($reflectionMethod->getAttributes(RequireLoggedInModeratorParticipant::class))) { $this->getLoggedIn($controller, true); } if (!empty($reflectionMethod->getAttributes(RequireParticipantOrLoggedInAndListedConversation::class))) { $this->getLoggedInOrGuest($controller, false, true); } $attributes = $reflectionMethod->getAttributes(RequireFederatedParticipant::class); if (!empty($attributes)) { $sessionIdParameter = $this->readSessionIdParameterFromAttributes($attributes); $this->getLoggedInOrGuest($controller, false, sessionIdParameter: $sessionIdParameter); } if (!empty($reflectionMethod->getAttributes(RequireParticipant::class))) { $this->getLoggedInOrGuest($controller, false); } if (!empty($reflectionMethod->getAttributes(RequireModeratorParticipant::class))) { $this->getLoggedInOrGuest($controller, true); } if (!empty($reflectionMethod->getAttributes(RequireRoom::class))) { $this->getRoom($controller); } if (empty($reflectionMethod->getAttributes(FederationSupported::class))) { // When federation is not supported, the room needs to be local $this->checkFederationSupport($controller); } if (!empty($reflectionMethod->getAttributes(RequireReadWriteConversation::class))) { $this->checkReadOnlyState($controller); } if (!empty($reflectionMethod->getAttributes(RequireModeratorOrNoLobby::class))) { $this->checkLobbyState($controller); } $requiredPermissions = $reflectionMethod->getAttributes(RequirePermission::class); if ($requiredPermissions) { foreach ($requiredPermissions as $attribute) { /** @var RequirePermission $requirement */ $requirement = $attribute->newInstance(); $this->checkPermission($controller, $requirement->getPermission()); } } } protected function readSessionIdParameterFromAttributes(array $attributes): ?string { foreach ($attributes as $attribute) { /** @var RequireFederatedParticipant $instance */ $instance = $attribute->newInstance(); if ($instance->getSessionIdParameter() !== null) { return $instance->getSessionIdParameter(); } } return null; } /** * @param AEnvironmentAwareOCSController $controller * @throws ForbiddenException */ protected function getRoom(AEnvironmentAwareOCSController $controller): void { $token = $this->request->getParam('token'); $room = $this->manager->getRoomByToken($token); $controller->setRoom($room); $this->banService->throwIfActorIsBanned($room, $this->userId); } /** * @param AEnvironmentAwareOCSController $controller * @param bool $moderatorRequired * @throws NotAModeratorException */ protected function getLoggedIn(AEnvironmentAwareOCSController $controller, bool $moderatorRequired): void { $token = $this->request->getParam('token'); $sessionId = $this->talkSession->getSessionForRoom($token); $room = $this->manager->getRoomForUserByToken($token, $this->userId, $sessionId); $controller->setRoom($room); $participant = $this->participantService->getParticipant($room, $this->userId, $sessionId); $controller->setParticipant($participant); $this->banService->throwIfActorIsBanned($room, $this->userId); if ($moderatorRequired && !$participant->hasModeratorPermissions(false)) { throw new NotAModeratorException(); } } /** * @param AEnvironmentAwareOCSController $controller * @param bool $moderatorRequired * @param bool $requireListedWhenNoParticipant * @throws NotAModeratorException * @throws ParticipantNotFoundException */ protected function getLoggedInOrGuest( AEnvironmentAwareOCSController $controller, bool $moderatorRequired, bool $requireListedWhenNoParticipant = false, bool $requireFederationWhenNotLoggedIn = false, ?string $sessionIdParameter = null, ): void { if ($requireFederationWhenNotLoggedIn && $this->userId === null && !$this->federationAuthenticator->isFederationRequest()) { throw new ParticipantNotFoundException(); } if ($sessionIdParameter !== null && !$this->federationAuthenticator->isFederationRequest()) { throw new ParticipantNotFoundException(); } $room = $controller->getRoom(); $sessionId = null; if (!$room instanceof Room) { $token = $this->request->getParam('token'); if (!$this->federationAuthenticator->isFederationRequest()) { $sessionId = $this->talkSession->getSessionForRoom($token); $room = $this->manager->getRoomForUserByToken($token, $this->userId, $sessionId); } else { $sessionId = $sessionIdParameter !== null ? $this->request->getParam($sessionIdParameter) : null; $room = $this->manager->getRoomByRemoteAccess($token, Attendee::ACTOR_FEDERATED_USERS, $this->federationAuthenticator->getCloudId(), $this->federationAuthenticator->getAccessToken(), $sessionId); // Get and set the participant already, so we don't retry public access $participant = $this->participantService->getParticipantByActor($room, Attendee::ACTOR_FEDERATED_USERS, $this->federationAuthenticator->getCloudId()); if ($sessionIdParameter !== null && !$participant->getSession() instanceof Session) { // If a session is required, fail if we didn't find it throw new ParticipantNotFoundException(); } $this->federationAuthenticator->authenticated($room, $participant); $controller->setParticipant($participant); } $controller->setRoom($room); } $participant = $controller->getParticipant(); if (!$participant instanceof Participant) { $participant = null; if ($sessionId !== null) { try { $participant = $this->participantService->getParticipantBySession($room, $sessionId); $controller->setParticipant($participant); } catch (ParticipantNotFoundException $e) { // ignore and fall back in case a concurrent request might have // invalidated the session } } if ($participant === null) { if (!$requireListedWhenNoParticipant || !$this->manager->isRoomListableByUser($room, $this->userId)) { $participant = $this->participantService->getParticipant($room, $this->userId); $controller->setParticipant($participant); } } } $this->banService->throwIfActorIsBanned($room, $this->userId); if ($moderatorRequired && !$participant->hasModeratorPermissions()) { throw new NotAModeratorException(); } } /** * @param AEnvironmentAwareOCSController $controller * @throws RoomNotFoundException * @throws ParticipantNotFoundException */ protected function getRoomByInvite(AEnvironmentAwareOCSController $controller): void { if ($this->userId === null) { throw new ParticipantNotFoundException('No user available'); } $room = $controller->getRoom(); if (!$room instanceof Room) { $token = $this->request->getParam('token'); $room = $this->manager->getRoomByToken($token); } $participant = $controller->getParticipant(); if (!$participant instanceof Participant) { try { $invitation = $this->invitationMapper->getInvitationForUserByLocalRoom($room, $this->userId); $controller->setRoom($room); $controller->setInvitation($invitation); } catch (DoesNotExistException $e) { throw new ParticipantNotFoundException('No invite available', $e->getCode(), $e); } } } /** * @param AEnvironmentAwareOCSController $controller * @throws FederationUnsupportedFeatureException */ protected function checkFederationSupport(AEnvironmentAwareOCSController $controller): void { $room = $controller->getRoom(); if ($room instanceof Room && $room->isFederatedConversation()) { throw new FederationUnsupportedFeatureException(); } } /** * @param AEnvironmentAwareOCSController $controller * @throws ReadOnlyException */ protected function checkReadOnlyState(AEnvironmentAwareOCSController $controller): void { $room = $controller->getRoom(); if (!$room instanceof Room || $room->getReadOnly() === Room::READ_ONLY) { throw new ReadOnlyException(); } if ($room->getType() === Room::TYPE_CHANGELOG) { throw new ReadOnlyException(); } } /** * @throws PermissionsException */ protected function checkPermission(AEnvironmentAwareOCSController $controller, string $permission): void { $participant = $controller->getParticipant(); if (!$participant instanceof Participant) { throw new PermissionsException(); } if ($permission === RequirePermission::CHAT && !($participant->getPermissions() & Attendee::PERMISSIONS_CHAT)) { throw new PermissionsException(); } if ($permission === RequirePermission::START_CALL && !($participant->getPermissions() & Attendee::PERMISSIONS_CALL_START)) { throw new PermissionsException(); } } /** * @throws LobbyException */ protected function checkLobbyState(AEnvironmentAwareOCSController $controller): void { try { $this->getLoggedInOrGuest($controller, true); return; } catch (NotAModeratorException|ParticipantNotFoundException) { } $participant = $controller->getParticipant(); if ($participant instanceof Participant && $participant->getPermissions() & Attendee::PERMISSIONS_LOBBY_IGNORE) { return; } $room = $controller->getRoom(); if (!$room instanceof Room || $room->getLobbyState() !== Webinary::LOBBY_NONE) { throw new LobbyException(); } } /** * @throws \Exception */ #[\Override] public function afterException(Controller $controller, string $methodName, \Exception $exception): Response { if ($exception instanceof RoomNotFoundException || $exception instanceof ParticipantNotFoundException) { if ($controller instanceof OCSController) { $reflectionMethod = new \ReflectionMethod($controller, $methodName); $attributes = $reflectionMethod->getAttributes(BruteForceProtection::class); if (!empty($attributes)) { foreach ($attributes as $attribute) { /** @var BruteForceProtection $protection */ $protection = $attribute->newInstance(); $action = $protection->getAction(); if ($action === 'talkRoomToken') { $this->logger->debug('User "' . ($this->userId ?? 'ANONYMOUS') . '" throttled for accessing "' . ($this->request->getParam('token') ?? 'UNKNOWN') . '"', ['app' => 'spreed-bfp']); try { $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action); } catch (MaxDelayReached $e) { throw new OCSException($e->getMessage(), Http::STATUS_TOO_MANY_REQUESTS); } $this->throttler->registerAttempt($action, $this->request->getRemoteAddress(), [ 'token' => $this->request->getParam('token') ?? '', ]); } } } throw new OCSException('', Http::STATUS_NOT_FOUND); } return new RedirectResponse($this->url->linkToDefaultPageUrl()); } if ($exception instanceof CannotReachRemoteException) { if ($controller instanceof OCSController) { throw new OCSException('', Http::STATUS_UNPROCESSABLE_ENTITY); } return new RedirectResponse($this->url->linkToDefaultPageUrl()); } if ($exception instanceof FederationUnsupportedFeatureException) { if ($controller instanceof OCSController) { throw new OCSException('', Http::STATUS_NOT_ACCEPTABLE); } return new RedirectResponse($this->url->linkToDefaultPageUrl()); } if ($exception instanceof LobbyException) { if ($controller instanceof OCSController) { throw new OCSException('', Http::STATUS_PRECONDITION_FAILED); } return new RedirectResponse($this->url->linkToDefaultPageUrl()); } if ($exception instanceof NotAModeratorException || $exception instanceof ReadOnlyException || $exception instanceof ForbiddenException || $exception instanceof PermissionsException) { if ($controller instanceof OCSController) { throw new OCSException('', Http::STATUS_FORBIDDEN); } return new RedirectResponse($this->url->linkToDefaultPageUrl()); } throw $exception; } }