diff options
author | Gary Kim <gary@garykim.dev> | 2021-07-31 04:03:34 +0300 |
---|---|---|
committer | Gary Kim <gary@garykim.dev> | 2021-09-20 21:30:08 +0300 |
commit | 886a7753d4424b22e00521a525b8b7d5035b7a30 (patch) | |
tree | ea2efa0e87b10ce0762f4798690d4f7a4960bee9 /lib | |
parent | 6d261cd902a1e16666eea0c14b8f67a9ef9973d2 (diff) |
Add sending room shares support
Signed-off-by: Gary Kim <gary@garykim.dev>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/BackgroundJob/RetryJob.php | 97 | ||||
-rw-r--r-- | lib/Chat/Notifier.php | 2 | ||||
-rw-r--r-- | lib/Controller/RoomController.php | 35 | ||||
-rw-r--r-- | lib/Exceptions/CannotReachRemoteException.php | 28 | ||||
-rw-r--r-- | lib/Federation/CloudFederationProviderTalk.php | 64 | ||||
-rw-r--r-- | lib/Federation/FederationManager.php | 32 | ||||
-rw-r--r-- | lib/Federation/Notifications.php | 216 | ||||
-rw-r--r-- | lib/Manager.php | 2 | ||||
-rw-r--r-- | lib/Model/AttendeeMapper.php | 18 | ||||
-rw-r--r-- | lib/Service/ParticipantService.php | 31 | ||||
-rw-r--r-- | lib/Service/RoomService.php | 4 |
11 files changed, 506 insertions, 23 deletions
diff --git a/lib/BackgroundJob/RetryJob.php b/lib/BackgroundJob/RetryJob.php new file mode 100644 index 000000000..0182cadea --- /dev/null +++ b/lib/BackgroundJob/RetryJob.php @@ -0,0 +1,97 @@ +<?php +/** + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2021 Gary Kim <gary@garykim.dev> + * + * @author Bjoern Schiessle <bjoern@schiessle.org> + * @author Björn Schießle <bjoern@schiessle.org> + * @author Joas Schilling <coding@schilljs.com> + * @author Lukas Reschke <lukas@statuscode.ch> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author Gary Kim <gary@garykim.dev> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\Talk\BackgroundJob; + +use OCA\Talk\Federation\Notifications; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\IJobList; +use OCP\BackgroundJob\Job; +use OCP\ILogger; + +/** + * Class RetryJob + * + * Background job to re-send update of federated re-shares to the remote server in + * case the server was not available on the first try + * + * @package OCA\Talk\BackgroundJob + */ +class RetryJob extends Job { + /** @var Notifications */ + private $notifications; + + /** @var int max number of attempts to send the request */ + private $maxTry = 20; + + /** @var int how much time should be between two tries (10 minutes) */ + private $interval = 600; + + + public function __construct(Notifications $notifications, + ITimeFactory $timeFactory) { + parent::__construct($timeFactory); + $this->notifications = $notifications; + } + + /** + * run the job, then remove it from the jobList + * + * @param IJobList $jobList + * @param ILogger|null $logger + */ + public function execute(IJobList $jobList, ILogger $logger = null) { + if (((int)$this->argument['try']) > $this->maxTry) { + $jobList->remove($this, $this->argument); + return; + } + if ($this->shouldRun($this->argument)) { + parent::execute($jobList, $logger); + $jobList->remove($this, $this->argument); + } + } + + protected function run($argument) { + $remote = $argument['remote']; + $data = json_decode($argument['data'], true); + $try = (int)$argument['try'] + 1; + + $this->notifications->sendUpdateDataToRemote($remote, $data, $try); + } + + /** + * test if it is time for the next run + * + * @param array $argument + * @return bool + */ + protected function shouldRun(array $argument) { + $lastRun = (int)$argument['lastRun']; + return (($this->time->getTime() - $lastRun) > $this->interval); + } +} diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php index b5e5ddd35..738044b9e 100644 --- a/lib/Chat/Notifier.php +++ b/lib/Chat/Notifier.php @@ -35,9 +35,9 @@ use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; use OCP\Comments\IComment; use OCP\IConfig; +use OCP\IUserManager; use OCP\Notification\IManager as INotificationManager; use OCP\Notification\INotification; -use OCP\IUserManager; /** * Helper class for notifications related to user mentions in chat messages. diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index b63c0b7c2..09102d13c 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -54,13 +54,14 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\IComment; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Federation\ICloudIdManager; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; use OCP\IL10N; use OCP\IRequest; use OCP\IUser; use OCP\IUserManager; -use OCP\IGroup; -use OCP\IGroupManager; -use OCP\IConfig; use OCP\User\Events\UserLiveStatusEvent; use OCP\UserStatus\IManager as IUserStatusManager; use OCP\UserStatus\IUserStatus; @@ -80,6 +81,8 @@ class RoomController extends AEnvironmentAwareController { protected $groupManager; /** @var Manager */ protected $manager; + /** @var ICloudIdManager */ + protected $cloudIdManager; /** @var RoomService */ protected $roomService; /** @var ParticipantService */ @@ -127,7 +130,8 @@ class RoomController extends AEnvironmentAwareController { ITimeFactory $timeFactory, IL10N $l10n, IConfig $config, - Config $talkConfig) { + Config $talkConfig, + ICloudIdManager $cloudIdManager) { parent::__construct($appName, $request); $this->session = $session; $this->appManager = $appManager; @@ -147,6 +151,7 @@ class RoomController extends AEnvironmentAwareController { $this->l10n = $l10n; $this->config = $config; $this->talkConfig = $talkConfig; + $this->cloudIdManager = $cloudIdManager; } protected function getTalkHashHeader(): array { @@ -1029,9 +1034,12 @@ class RoomController extends AEnvironmentAwareController { $participants = $this->participantService->getParticipantsForRoom($this->room); $participantsByUserId = []; + $remoteParticipantsByFederatedId = []; foreach ($participants as $participant) { if ($participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS) { $participantsByUserId[$participant->getAttendee()->getActorId()] = $participant; + } elseif ($participant->getAttendee()->getAccessToken() === Attendee::ACTOR_FEDERATED_USERS) { + $remoteParticipantsByFederatedId[$participant->getAttendee()->getActorId()] = $participant; } } @@ -1084,6 +1092,18 @@ class RoomController extends AEnvironmentAwareController { $this->guestManager->sendEmailInvitation($this->room, $participant); return new DataResponse($data); + } elseif ($source === 'remote') { + try { + $newUser = $this->cloudIdManager->resolveCloudId($newParticipant); + } catch (\InvalidArgumentException $e) { + return new DataResponse([], Http::STATUS_BAD_REQUEST); + } + + $participantsToAdd[] = [ + 'actorType' => Attendee::ACTOR_FEDERATED_USERS, + 'actorId' => $newUser->getId() . '@' . $newUser->getRemote(), + 'displayName' => $newUser->getDisplayId(), + ]; } else { return new DataResponse([], Http::STATUS_BAD_REQUEST); } @@ -1092,6 +1112,9 @@ class RoomController extends AEnvironmentAwareController { // existing users with USER_SELF_JOINED will get converted to regular USER participants foreach ($participantsToAdd as $index => $participantToAdd) { $existingParticipant = $participantsByUserId[$participantToAdd['actorId']] ?? null; + if ($participantToAdd['actorType'] === Attendee::ACTOR_FEDERATED_USERS) { + $existingParticipant = $remoteParticipantsByFederatedId[$participantToAdd['actorId']] ?? null; + } if ($existingParticipant !== null) { unset($participantsToAdd[$index]); @@ -1103,8 +1126,10 @@ class RoomController extends AEnvironmentAwareController { } } + $addedBy = $this->userManager->get($this->userId); + // add the remaining users in batch - $this->participantService->addUsers($this->room, $participantsToAdd); + $this->participantService->addUsers($this->room, $participantsToAdd, $addedBy); return new DataResponse([]); } diff --git a/lib/Exceptions/CannotReachRemoteException.php b/lib/Exceptions/CannotReachRemoteException.php new file mode 100644 index 000000000..81f65934f --- /dev/null +++ b/lib/Exceptions/CannotReachRemoteException.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2021 Gary Kim <gary@garykim.dev> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\Talk\Exceptions; + +class CannotReachRemoteException extends \Exception { +} diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php index 3e89bda07..daf85efad 100644 --- a/lib/Federation/CloudFederationProviderTalk.php +++ b/lib/Federation/CloudFederationProviderTalk.php @@ -116,8 +116,9 @@ class CloudFederationProviderTalk implements ICloudFederationProvider { // TODO: Implement group shares } - if (!is_numeric($share->getShareType())) { - throw new ProviderCouldNotAddShareException('shareType is not a number', '', Http::STATUS_BAD_REQUEST); + $roomType = $share->getProtocol()['roomType']; + if (!is_numeric($roomType) || !in_array((int) $roomType, $this->validSharedRoomTypes(), true)) { + throw new ProviderCouldNotAddShareException('roomType is not a valid number', '', Http::STATUS_BAD_REQUEST); } $shareSecret = $share->getShareSecret(); @@ -125,7 +126,7 @@ class CloudFederationProviderTalk implements ICloudFederationProvider { $remoteId = $share->getProviderId(); $roomToken = $share->getResourceName(); $roomName = $share->getProtocol()['roomName']; - $roomType = (int) $share->getShareType(); + $roomType = (int) $roomType; $sharedBy = $share->getSharedByDisplayName(); $sharedByFederatedId = $share->getSharedBy(); $owner = $share->getOwnerDisplayName(); @@ -166,7 +167,7 @@ class CloudFederationProviderTalk implements ICloudFederationProvider { case 'SHARE_DECLINED': return $this->shareDeclined((int) $providerId, $notification); case 'SHARE_UNSHARED': - return []; // TODO: Implement + return $this->shareUnshared((int) $providerId, $notification); case 'REQUEST_RESHARE': return []; // TODO: Implement case 'RESHARE_UNDO': @@ -206,6 +207,26 @@ class CloudFederationProviderTalk implements ICloudFederationProvider { } /** + * @throws ActionNotSupportedException + * @throws ShareNotFound + * @throws AuthenticationFailedException + */ + private function shareUnshared(int $id, array $notification): array { + $attendee = $this->getRemoteAttendeeAndValidate($id, $notification['sharedSecret']); + + $room = $this->manager->getRoomById($attendee->getRoomId()); + + // Sanity check to make sure the room is a remote room + if (!$room->isFederatedRemoteRoom()) { + throw new ShareNotFound(); + } + + $participant = new Participant($room, $attendee, null); + $this->participantService->removeAttendee($room, $participant, Room::PARTICIPANT_REMOVED); + return []; + } + + /** * @throws AuthenticationFailedException * @throws ActionNotSupportedException * @throws ShareNotFound @@ -229,6 +250,31 @@ class CloudFederationProviderTalk implements ICloudFederationProvider { return $attendee; } + /** + * @param int $id + * @param string $sharedSecret + * @return Attendee + * @throws ActionNotSupportedException + * @throws ShareNotFound + * @throws AuthenticationFailedException + */ + private function getRemoteAttendeeAndValidate(int $id, string $sharedSecret): Attendee { + if (!$this->federationManager->isEnabled()) { + throw new ActionNotSupportedException('Server does not support Talk federation'); + } + + if (!$sharedSecret) { + throw new AuthenticationFailedException(); + } + + try { + $attendee = $this->attendeeMapper->getByRemoteIdAndToken($id, $sharedSecret); + } catch (Exception $ex) { + throw new ShareNotFound(); + } + return $attendee; + } + private function notifyAboutNewShare(IUser $shareWith, string $shareId, string $sharedByFederatedId, string $sharedByName, string $roomName, string $roomToken, string $serverUrl) { $notification = $this->notificationManager->createNotification(); $notification->setApp(Application::APP_ID) @@ -256,10 +302,18 @@ class CloudFederationProviderTalk implements ICloudFederationProvider { $this->notificationManager->notify($notification); } + private function validSharedRoomTypes(): array { + return [ + Room::TYPE_ONE_TO_ONE, + Room::TYPE_GROUP, + Room::TYPE_PUBLIC, + ]; + } + /** * @inheritDoc */ - public function getSupportedShareTypes() { + public function getSupportedShareTypes(): array { return ['user']; } } diff --git a/lib/Federation/FederationManager.php b/lib/Federation/FederationManager.php index 691d83a6c..d43a30536 100644 --- a/lib/Federation/FederationManager.php +++ b/lib/Federation/FederationManager.php @@ -26,6 +26,7 @@ declare(strict_types=1); namespace OCA\Talk\Federation; use OCA\Talk\AppInfo\Application; +use OCA\Talk\Exceptions\CannotReachRemoteException; use OCA\Talk\Exceptions\RoomNotFoundException; use OCA\Talk\Exceptions\UnauthorizedException; use OCA\Talk\Manager; @@ -34,6 +35,7 @@ use OCA\Talk\Model\Invitation; use OCA\Talk\Model\InvitationMapper; use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\DB\Exception as DBException; use OCP\IConfig; @@ -47,6 +49,9 @@ use OCP\IUser; * FederationManager handles incoming federated rooms */ class FederationManager { + public const TALK_ROOM_RESOURCE = 'talk-room'; + public const TOKEN_LENGTH = 15; + /** @var IConfig */ private $config; @@ -59,16 +64,21 @@ class FederationManager { /** @var InvitationMapper */ private $invitationMapper; + /** @var Notifications */ + private $notifications; + public function __construct( IConfig $config, Manager $manager, ParticipantService $participantService, - InvitationMapper $invitationMapper + InvitationMapper $invitationMapper, + Notifications $notifications ) { $this->config = $config; $this->manager = $manager; $this->participantService = $participantService; $this->invitationMapper = $invitationMapper; + $this->notifications = $notifications; } /** @@ -82,6 +92,7 @@ class FederationManager { /** * @param IUser $user + * @param string $remoteId * @param int $roomType * @param string $roomName * @param string $roomToken @@ -110,6 +121,8 @@ class FederationManager { * @throws DBException * @throws UnauthorizedException * @throws MultipleObjectsReturnedException + * @throws DoesNotExistException + * @throws CannotReachRemoteException */ public function acceptRemoteRoomShare(IUser $user, int $shareId) { $invitation = $this->invitationMapper->getInvitationById($shareId); @@ -119,6 +132,13 @@ class FederationManager { // Add user to the room $room = $this->manager->getRoomById($invitation->getRoomId()); + if ( + !$this->notifications->sendShareAccepted($room->getServerUrl(), $invitation->getRemoteId(), $invitation->getAccessToken()) + ) { + throw new CannotReachRemoteException(); + } + + $participant = [ [ 'actorType' => Attendee::ACTOR_USERS, @@ -128,26 +148,28 @@ class FederationManager { 'remoteId' => $invitation->getRemoteId(), ] ]; - $this->participantService->addUsers($room, $participant); + $this->participantService->addUsers($room, $participant, $user); $this->invitationMapper->delete($invitation); - - // TODO: Send SHARE_ACCEPTED notification } /** * @throws DBException * @throws UnauthorizedException * @throws MultipleObjectsReturnedException + * @throws DoesNotExistException */ public function rejectRemoteRoomShare(IUser $user, int $shareId) { $invitation = $this->invitationMapper->getInvitationById($shareId); if ($invitation->getUserId() !== $user->getUID()) { throw new UnauthorizedException('invitation is for a different user'); } + + $room = $this->manager->getRoomById($invitation->getRoomId()); + $this->invitationMapper->delete($invitation); - // TODO: Send SHARE_DECLINED notification + $this->notifications->sendShareDeclined($room->getServerUrl(), $invitation->getRemoteId(), $invitation->getAccessToken()); } /** diff --git a/lib/Federation/Notifications.php b/lib/Federation/Notifications.php new file mode 100644 index 000000000..fa0963f7e --- /dev/null +++ b/lib/Federation/Notifications.php @@ -0,0 +1,216 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2021 Gary Kim <gary@garykim.dev> + * + * @author Gary Kim <gary@garykim.dev> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Talk\Federation; + +use OCA\FederatedFileSharing\AddressHandler; +use OCA\Talk\AppInfo\Application; +use OCA\Talk\BackgroundJob\RetryJob; +use OCP\BackgroundJob\IJobList; +use OCP\Federation\ICloudFederationFactory; +use OCP\Federation\ICloudFederationNotification; +use OCP\Federation\ICloudFederationProviderManager; +use Psr\Log\LoggerInterface; + +class Notifications { + /** @var ICloudFederationFactory */ + private $cloudFederationFactory; + + /** @var AddressHandler */ + private $addressHandler; + + /** @var LoggerInterface */ + private $logger; + + /** @var ICloudFederationProviderManager */ + private $federationProviderManager; + + /** @var IJobList */ + private $jobList; + + public function __construct( + ICloudFederationFactory $cloudFederationFactory, + AddressHandler $addressHandler, + LoggerInterface $logger, + ICloudFederationProviderManager $federationProviderManager, + IJobList $jobList + ) { + $this->cloudFederationFactory = $cloudFederationFactory; + $this->addressHandler = $addressHandler; + $this->logger = $logger; + $this->federationProviderManager = $federationProviderManager; + $this->jobList = $jobList; + } + + public function sendRemoteShare(string $providerId, string $token, string $shareWith, string $name, string $owner, string $ownerFederatedId, + string $sharedBy, string $sharedByFederatedId, string $shareType, string $roomName, string $roomType): bool { + [$user, $remote] = $this->addressHandler->splitUserRemote($shareWith); + + if (!($user && $remote)) { + $this->logger->info( + "could not share $name, invalid contact $shareWith", + ['app' => Application::APP_ID] + ); + return false; + } + + $remote = $this->prepareRemoteUrl($remote); + + $share = $this->cloudFederationFactory->getCloudFederationShare( + $user . '@' . $remote, + $name, + '', + $providerId, + $ownerFederatedId, + $owner, + $sharedByFederatedId, + $sharedBy, + $token, + $shareType, + FederationManager::TALK_ROOM_RESOURCE + ); + + // Put room name info in the share + $protocol = $share->getProtocol(); + $protocol['roomName'] = $roomName; + $protocol['roomType'] = $roomType; + $protocol['name'] = 'nctalk'; + $share->setProtocol($protocol); + + $response = $this->federationProviderManager->sendShare($share); + if (is_array($response)) { + return true; + } + $this->logger->info( + "failed sharing $name with $shareWith", + ['app' => Application::APP_ID] + ); + + return false; + } + + /** + * send remote share acceptance notification to remote server + * + * @param string $remote remote server domain + * @param string $id share id + * @param string $token share secret token + * @return bool success + */ + public function sendShareAccepted(string $remote, string $id, string $token): bool { + $remote = $this->prepareRemoteUrl($remote); + + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + 'SHARE_ACCEPTED', + FederationManager::TALK_ROOM_RESOURCE, + $id, + [ + 'sharedSecret' => $token, + 'message' => 'Recipient accepted the share', + ]); + $response = $this->federationProviderManager->sendNotification($remote, $notification); + if (!is_array($response)) { + $this->logger->info( + "failed to send share accepted notification for share from $remote", + ['app' => Application::APP_ID] + ); + return false; + } + return true; + } + + public function sendShareDeclined(string $remote, string $id, string $token): bool { + $remote = $this->prepareRemoteUrl($remote); + + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + 'SHARE_DECLINED', + FederationManager::TALK_ROOM_RESOURCE, + $id, + [ + 'sharedSecret' => $token, + 'message' => 'Recipient declined the share', + ] + ); + $response = $this->federationProviderManager->sendNotification($remote, $notification); + if (!is_array($response)) { + $this->logger->info( + "failed to send share declined notification for share from $remote", + ['app' => Application::APP_ID] + ); + return false; + } + return true; + } + + public function sendRemoteUnShare(string $remote, string $id, string $token) { + $remote = $this->prepareRemoteUrl($remote); + + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + 'SHARE_UNSHARED', + FederationManager::TALK_ROOM_RESOURCE, + $id, + [ + 'sharedSecret' => $token, + 'message' => 'This room has been unshared', + ] + ); + + $this->sendUpdateToRemote($remote, $notification); + } + + public function sendUpdateDataToRemote(string $remote, array $data = [], int $try = 0) { + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + $data['notificationType'], + $data['resourceType'], + $data['providerId'], + $data['notification'] + ); + $this->sendUpdateToRemote($remote, $notification, $try); + } + + public function sendUpdateToRemote(string $remote, ICloudFederationNotification $notification, int $try = 0) { + $response = $this->federationProviderManager->sendNotification($remote, $notification); + if (!is_array($response)) { + $this->jobList->add(RetryJob::class, + [ + 'remote' => $remote, + 'data' => json_encode($notification->getMessage()), + 'try' => $try, + ] + ); + } + } + + private function prepareRemoteUrl(string $remote): string { + if ($this->addressHandler->urlContainProtocol($remote)) { + return "https://" . $remote; + } + return $remote; + } +} diff --git a/lib/Manager.php b/lib/Manager.php index ecbfdd8cd..1de00d120 100644 --- a/lib/Manager.php +++ b/lib/Manager.php @@ -43,10 +43,10 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\ICache; use OCP\IConfig; use OCP\IDBConnection; +use OCP\IGroupManager; use OCP\IL10N; use OCP\IUser; use OCP\IUserManager; -use OCP\IGroupManager; use OCP\Security\IHasher; use OCP\Security\ISecureRandom; diff --git a/lib/Model/AttendeeMapper.php b/lib/Model/AttendeeMapper.php index 261b949b0..2599e9d9d 100644 --- a/lib/Model/AttendeeMapper.php +++ b/lib/Model/AttendeeMapper.php @@ -79,6 +79,24 @@ class AttendeeMapper extends QBMapper { } /** + * @param int $id + * @param string $token + * @return Attendee + * @throws DBException + * @throws DoesNotExistException + * @throws MultipleObjectsReturnedException + */ + public function getByRemoteIdAndToken(int $id, string $token): Attendee { + $query = $this->db->getQueryBuilder(); + $query->select('*') + ->from($this->getTableName()) + ->where($query->expr()->eq('id', $query->createNamedParameter($id))) + ->andWhere($query->expr()->eq('access_token', $query->createNamedParameter($token))); + + return $this->findEntity($query); + } + + /** * @param int $roomId * @param string $actorType * @param int|null $lastJoinedCall diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index 89dd94106..02046563e 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -40,6 +40,8 @@ use OCA\Talk\Events\RoomEvent; use OCA\Talk\Exceptions\InvalidPasswordException; use OCA\Talk\Exceptions\ParticipantNotFoundException; use OCA\Talk\Exceptions\UnauthorizedException; +use OCA\Talk\Federation\FederationManager; +use OCA\Talk\Federation\Notifications; use OCA\Talk\Model\Attendee; use OCA\Talk\Model\AttendeeMapper; use OCA\Talk\Model\SelectHelper; @@ -57,9 +59,9 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroup; +use OCP\IGroupManager; use OCP\IUser; use OCP\IUserManager; -use OCP\IGroupManager; use OCP\Security\ISecureRandom; class ParticipantService { @@ -85,6 +87,8 @@ class ParticipantService { private $groupManager; /** @var MembershipService */ private $membershipService; + /** @var Notifications */ + private $notifications; /** @var ITimeFactory */ private $timeFactory; @@ -99,6 +103,7 @@ class ParticipantService { IUserManager $userManager, IGroupManager $groupManager, MembershipService $membershipService, + Notifications $notifications, ITimeFactory $timeFactory) { $this->serverConfig = $serverConfig; $this->talkConfig = $talkConfig; @@ -112,6 +117,7 @@ class ParticipantService { $this->groupManager = $groupManager; $this->membershipService = $membershipService; $this->timeFactory = $timeFactory; + $this->notifications = $notifications; } public function updateParticipantType(Room $room, Participant $participant, int $participantType): void { @@ -221,7 +227,7 @@ class ParticipantService { 'displayName' => $user->getDisplayName(), // need to use "USER" here, because "USER_SELF_JOINED" only works for public calls 'participantType' => Participant::USER, - ]]); + ]], $user); } elseif ($room->getType() === Room::PUBLIC_CALL) { // User joining a public room, without being invited $this->addUsers($room, [[ @@ -229,7 +235,7 @@ class ParticipantService { 'actorId' => $user->getUID(), 'displayName' => $user->getDisplayName(), 'participantType' => Participant::USER_SELF_JOINED, - ]]); + ]], $user); } else { // shouldn't happen unless some code called joinRoom without previous checks throw new UnauthorizedException('Participant is not allowed to join'); @@ -304,8 +310,9 @@ class ParticipantService { /** * @param Room $room * @param array $participants + * @param IUser|null $addedBy */ - public function addUsers(Room $room, array $participants): void { + public function addUsers(Room $room, array $participants, ?IUser $addedBy = null): void { if (empty($participants)) { return; } @@ -322,6 +329,14 @@ class ParticipantService { $readPrivacy = Participant::PRIVACY_PUBLIC; if ($participant['actorType'] === Attendee::ACTOR_USERS) { $readPrivacy = $this->talkConfig->getUserReadPrivacy($participant['actorId']); + } elseif ($participant['actorType'] === Attendee::ACTOR_FEDERATED_USERS) { + if ($addedBy === null) { + continue; + } + $participant['accessToken'] = $this->secureRandom->generate( + FederationManager::TOKEN_LENGTH, + ISecureRandom::CHAR_HUMAN_READABLE + ); } $attendee = new Attendee(); @@ -348,6 +363,10 @@ class ParticipantService { throw $e; } } + + if ($attendee->getActorType() === Attendee::ACTOR_FEDERATED_USERS) { + $this->sendRemoteShare($room, $addedBy, $participant['actorId'], $participant['accessToken'], $entity->getId()); + } } if (!empty($attendees)) { @@ -358,6 +377,10 @@ class ParticipantService { } } + private function sendRemoteShare(Room $room, IUser $addedBy, string $addingUserId, string $token, int $attendeeId) { + $this->notifications->sendRemoteShare((string) $attendeeId, $token, $addingUserId, $room->getToken(), $addedBy->getDisplayName(), $addedBy->getCloudId(), $addedBy->getDisplayName(), $addedBy->getCloudId(), 'user', $room->getName(), (string) $room->getType()); + } + /** * @param Room $room * @param IGroup $group diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index 5e1000af7..9667f7193 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -77,7 +77,7 @@ class RoomService { 'displayName' => $targetUser->getDisplayName(), 'participantType' => Participant::OWNER, ], - ]); + ], $actor); } return $room; @@ -130,7 +130,7 @@ class RoomService { 'actorType' => Attendee::ACTOR_USERS, 'actorId' => $owner->getUID(), 'participantType' => Participant::OWNER, - ]]); + ]], null); } return $room; |