diff --git a/lib/Chat/Parser/SystemMessage.php b/lib/Chat/Parser/SystemMessage.php index fdd428b28c0..7c931003396 100644 --- a/lib/Chat/Parser/SystemMessage.php +++ b/lib/Chat/Parser/SystemMessage.php @@ -157,7 +157,13 @@ public function parseMessage(IComment $comment): array { } else if ($message === 'user_added') { $parsedParameters['user'] = $this->getUser($parameters['user']); $parsedMessage = $this->l->t('{actor} added {user}'); - if ($currentUserIsActor) { + if ($parsedParameters['user']['id'] === $parsedParameters['actor']['id']) { + if ($currentUserIsActor) { + $parsedMessage = $this->l->t('You joined the conversation'); + } else { + $parsedMessage = $this->l->t('{actor} joined the conversation'); + } + } else if ($currentUserIsActor) { $parsedMessage = $this->l->t('You added {user}'); } else if ($this->recipient instanceof IUser && $this->recipient->getUID() === $parsedParameters['user']['id']) { $parsedMessage = $this->l->t('{actor} added you'); @@ -165,7 +171,11 @@ public function parseMessage(IComment $comment): array { } else if ($message === 'user_removed') { $parsedParameters['user'] = $this->getUser($parameters['user']); if ($parsedParameters['user']['id'] === $parsedParameters['actor']['id']) { - $parsedMessage = $this->l->t('{actor} left the conversation'); + if ($currentUserIsActor) { + $parsedMessage = $this->l->t('You left the conversation'); + } else { + $parsedMessage = $this->l->t('{actor} left the conversation'); + } } else { $parsedMessage = $this->l->t('{actor} removed {user}'); if ($currentUserIsActor) { diff --git a/lib/Chat/SystemMessage/Listener.php b/lib/Chat/SystemMessage/Listener.php index 65da003545a..22a984e865f 100644 --- a/lib/Chat/SystemMessage/Listener.php +++ b/lib/Chat/SystemMessage/Listener.php @@ -116,7 +116,7 @@ public function register() { /** @var Room $room */ $room = $event->getSubject(); foreach ($participants as $participant) { - if ($userId !== $participant['userId']) { + if ($room->getObjectType() === 'file' || $userId !== $participant['userId']) { $this->sendSystemMessage($room, 'user_added', ['user' => $participant['userId']]); } } diff --git a/lib/Controller/FilesController.php b/lib/Controller/FilesController.php index a706b8404a0..2a06817335c 100644 --- a/lib/Controller/FilesController.php +++ b/lib/Controller/FilesController.php @@ -24,6 +24,7 @@ namespace OCA\Spreed\Controller; +use OCA\Spreed\Exceptions\ParticipantNotFoundException; use OCA\Spreed\Exceptions\RoomNotFoundException; use OCA\Spreed\Files\Util; use OCA\Spreed\Manager; @@ -75,8 +76,10 @@ public function __construct( * If there is no room associated to the given file id a new room is * created; the new room is a public room associated with a "file" object * with the given file id. Unlike normal rooms in which the owner is the - * user that created the room these are special rooms without owner or any - * other persistent participant. + * user that created the room these are special rooms without owner + * (although self joined users become persistent participants automatically + * when they join until they explicitly leave or no longer have access to + * the file). * * In any case, to create or even get the token of the room, the file must * be shared and the user must have direct access to that file; an error @@ -101,6 +104,12 @@ public function getRoom(string $fileId): DataResponse { $room = $this->manager->createPublicRoom($node->getName(), 'file', $fileId); } + try { + $room->getParticipant($this->currentUser); + } catch (ParticipantNotFoundException $e) { + $room->addUsers(['userId' => $this->currentUser]); + } + return new DataResponse([ 'token' => $room->getToken() ]); diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 73750a49052..81c4c7f6ff2 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -31,6 +31,7 @@ use OCA\Spreed\Exceptions\InvalidPasswordException; use OCA\Spreed\Exceptions\ParticipantNotFoundException; use OCA\Spreed\Exceptions\RoomNotFoundException; +use OCA\Spreed\Exceptions\UnauthorizedException; use OCA\Spreed\GuestManager; use OCA\Spreed\Manager; use OCA\Spreed\Participant; @@ -1084,14 +1085,17 @@ public function joinRoom(string $token, string $password = ''): DataResponse { return new DataResponse([], Http::STATUS_NOT_FOUND); } + $user = $this->userManager->get($this->userId); try { - if ($this->userId !== null) { - $newSessionId = $room->joinRoom($this->userId, $password, $this->session->getPasswordForRoom($token) === $room->getToken()); + if ($user instanceof IUser) { + $newSessionId = $room->joinRoom($user, $password, $this->session->getPasswordForRoom($token) === $room->getToken()); } else { $newSessionId = $room->joinRoomGuest($password, $this->session->getPasswordForRoom($token) === $room->getToken()); } } catch (InvalidPasswordException $e) { return new DataResponse([], Http::STATUS_FORBIDDEN); + } catch (UnauthorizedException $e) { + return new DataResponse([], Http::STATUS_NOT_FOUND); } $this->session->removePasswordForRoom($token); diff --git a/lib/Exceptions/UnauthorizedException.php b/lib/Exceptions/UnauthorizedException.php new file mode 100644 index 00000000000..fb297212bf2 --- /dev/null +++ b/lib/Exceptions/UnauthorizedException.php @@ -0,0 +1,27 @@ + + * + * @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 . + * + */ + + +namespace OCA\Spreed\Exceptions; + +class UnauthorizedException extends \Exception { + +} diff --git a/lib/Files/Listener.php b/lib/Files/Listener.php index 965c662d152..5ee4016a843 100644 --- a/lib/Files/Listener.php +++ b/lib/Files/Listener.php @@ -24,6 +24,7 @@ namespace OCA\Spreed\Files; +use OCA\Spreed\Exceptions\UnauthorizedException; use OCA\Spreed\Room; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -35,8 +36,9 @@ * specific shared file, for example, when collaboratively editing it. The room * is persistent and can be accessed simultaneously by any user with direct * access (user, group, circle and room share, but not link share, for example) - * to that file. The room has no owner, and there are no persistent participants - * (it is a public room that users join and leave on each session). + * to that file. The room has no owner, although self joined users become + * persistent participants automatically when they join until they explicitly + * leave or no longer have access to the file. * * These rooms are associated to a "file" object, and their custom behaviour is * provided by calling the methods of this class as a response to different room @@ -58,14 +60,22 @@ public function register() { $listener = function(GenericEvent $event) { /** @var Room $room */ $room = $event->getSubject(); - $this->preventUsersWithoutDirectAccessToTheFileFromJoining($room, $event->getArgument('userId')); + try { + $this->preventUsersWithoutDirectAccessToTheFileFromJoining($room, $event->getArgument('userId')); + } catch (UnauthorizedException $e) { + $event->setArgument('cancel', true); + } }; $this->dispatcher->addListener(Room::class . '::preJoinRoom', $listener); $listener = function(GenericEvent $event) { /** @var Room $room */ $room = $event->getSubject(); - $this->preventGuestsFromJoining($room); + try { + $this->preventGuestsFromJoining($room); + } catch (UnauthorizedException $e) { + $event->setArgument('cancel', true); + } }; $this->dispatcher->addListener(Room::class . '::preJoinRoomGuest', $listener); } @@ -82,7 +92,7 @@ public function register() { * * @param Room $room * @param string $userId - * @throws \Exception + * @throws UnauthorizedException */ public function preventUsersWithoutDirectAccessToTheFileFromJoining(Room $room, string $userId) { if ($room->getObjectType() !== 'file') { @@ -91,7 +101,7 @@ public function preventUsersWithoutDirectAccessToTheFileFromJoining(Room $room, $share = $this->util->getAnyDirectShareOfFileAccessibleByUser($room->getObjectId(), $userId); if (!$share) { - throw new \Exception('User does not have direct access to the file'); + throw new UnauthorizedException('User does not have direct access to the file'); } } @@ -101,14 +111,14 @@ public function preventUsersWithoutDirectAccessToTheFileFromJoining(Room $room, * This method should be called before a guest joins a room. * * @param Room $room - * @throws \Exception + * @throws UnauthorizedException */ public function preventGuestsFromJoining(Room $room) { if ($room->getObjectType() !== 'file') { return; } - throw new \Exception('Guests are not allowed in rooms for files'); + throw new UnauthorizedException('Guests are not allowed in rooms for files'); } } diff --git a/lib/Room.php b/lib/Room.php index 51e0c770c5a..bc1b4cb2275 100644 --- a/lib/Room.php +++ b/lib/Room.php @@ -27,6 +27,7 @@ use OCA\Spreed\Exceptions\InvalidPasswordException; use OCA\Spreed\Exceptions\ParticipantNotFoundException; +use OCA\Spreed\Exceptions\UnauthorizedException; use OCP\Comments\IComment; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -596,24 +597,31 @@ public function removeParticipantBySession(Participant $participant) { } /** - * @param string $userId + * @param IUser $user * @param string $password * @param bool $passedPasswordProtection * @return string * @throws InvalidPasswordException + * @throws UnauthorizedException */ - public function joinRoom($userId, $password, $passedPasswordProtection = false) { - $this->dispatcher->dispatch(self::class . '::preJoinRoom', new GenericEvent($this, [ - 'userId' => $userId, + public function joinRoom(IUser $user, $password, $passedPasswordProtection = false) { + $event = new GenericEvent($this, [ + 'userId' => $user->getUID(), 'password' => $password, 'passedPasswordProtection' => $passedPasswordProtection, - ])); + ]); + $this->dispatcher->dispatch(self::class . '::preJoinRoom', $event); + + if ($event->hasArgument('cancel') && $event->getArgument('cancel') === true) { + $this->removeUser($user); + throw new UnauthorizedException('Participant is not allowed to join'); + } $query = $this->db->getQueryBuilder(); $query->update('talk_participants') ->set('session_id', $query->createParameter('session_id')) ->where($query->expr()->eq('room_id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT))) - ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId))); + ->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($user->getUID()))); $sessionId = $this->secureRandom->generate(255); $query->setParameter('session_id', $sessionId); @@ -626,7 +634,7 @@ public function joinRoom($userId, $password, $passedPasswordProtection = false) // User joining a public room, without being invited $this->addUsers([ - 'userId' => $userId, + 'userId' => $user->getUID(), 'participantType' => Participant::USER_SELF_JOINED, 'sessionId' => $sessionId, ]); @@ -639,7 +647,7 @@ public function joinRoom($userId, $password, $passedPasswordProtection = false) } $this->dispatcher->dispatch(self::class . '::postJoinRoom', new GenericEvent($this, [ - 'userId' => $userId, + 'userId' => $user->getUID(), 'password' => $password, 'passedPasswordProtection' => $passedPasswordProtection, ])); @@ -692,9 +700,15 @@ public function leaveRoom($userId) { * @param bool $passedPasswordProtection * @return string * @throws InvalidPasswordException + * @throws UnauthorizedException */ public function joinRoomGuest($password, $passedPasswordProtection = false) { - $this->dispatcher->dispatch(self::class . '::preJoinRoomGuest', new GenericEvent($this)); + $event = new GenericEvent($this); + $this->dispatcher->dispatch(self::class . '::preJoinRoomGuest', $event); + + if ($event->hasArgument('cancel') && $event->getArgument('cancel') === true) { + throw new UnauthorizedException('Participant is not allowed to join'); + } if (!$passedPasswordProtection && !$this->verifyPassword($password)['result']) { throw new InvalidPasswordException();