Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorJoas Schilling <213943+nickvergessen@users.noreply.github.com>2021-10-04 18:26:17 +0300
committerGitHub <noreply@github.com>2021-10-04 18:26:17 +0300
commit820342ee28a919da20442561e64eee2b559f1650 (patch)
tree970dea8ff65cff59a9bde7a21ee784b24459c2e7 /lib
parentd9903b759e081602cda7ee8a23c01f48dca2b357 (diff)
parent257d026b6857f428fe7c00513213570b909546c0 (diff)
Merge pull request #6105 from nextcloud/enh/5723/sending-shares
Add sending room shares support
Diffstat (limited to 'lib')
-rw-r--r--lib/BackgroundJob/RetryJob.php101
-rw-r--r--lib/Chat/Notifier.php2
-rw-r--r--lib/Controller/RoomController.php41
-rw-r--r--lib/Exceptions/CannotReachRemoteException.php28
-rw-r--r--lib/Exceptions/RoomHasNoModeratorException.php28
-rw-r--r--lib/Federation/CloudFederationProviderTalk.php64
-rw-r--r--lib/Federation/FederationManager.php35
-rw-r--r--lib/Federation/Notifications.php244
-rw-r--r--lib/Manager.php2
-rw-r--r--lib/Model/AttendeeMapper.php39
-rw-r--r--lib/Service/ParticipantService.php54
-rw-r--r--lib/Service/RoomService.php4
12 files changed, 616 insertions, 26 deletions
diff --git a/lib/BackgroundJob/RetryJob.php b/lib/BackgroundJob/RetryJob.php
new file mode 100644
index 000000000..f5b1fc417
--- /dev/null
+++ b/lib/BackgroundJob/RetryJob.php
@@ -0,0 +1,101 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @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;
+
+
+ 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): void {
+ $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): bool {
+ $lastRun = (int)$argument['lastRun'];
+ $try = (int)$argument['try'];
+ return (($this->time->getTime() - $lastRun) > $this->nextRunBreak($try));
+ }
+
+ protected function nextRunBreak(int $try): int {
+ return min(($try + 1) * 300, 3600);
+ }
+}
diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php
index 277305a35..deb8eb5f5 100644
--- a/lib/Chat/Notifier.php
+++ b/lib/Chat/Notifier.php
@@ -36,9 +36,9 @@ use OCA\Talk\Service\ParticipantService;
use OCP\AppFramework\Utility\ITimeFactory;
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 d5063d167..34d68b573 100644
--- a/lib/Controller/RoomController.php
+++ b/lib/Controller/RoomController.php
@@ -36,6 +36,7 @@ use OCA\Talk\Exceptions\InvalidPasswordException;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Exceptions\UnauthorizedException;
+use OCA\Talk\Federation\FederationManager;
use OCA\Talk\GuestManager;
use OCA\Talk\Manager;
use OCA\Talk\MatterbridgeManager;
@@ -54,13 +55,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 +82,8 @@ class RoomController extends AEnvironmentAwareController {
protected $groupManager;
/** @var Manager */
protected $manager;
+ /** @var ICloudIdManager */
+ protected $cloudIdManager;
/** @var RoomService */
protected $roomService;
/** @var ParticipantService */
@@ -104,6 +108,8 @@ class RoomController extends AEnvironmentAwareController {
protected $config;
/** @var Config */
protected $talkConfig;
+ /** @var FederationManager */
+ protected $federationManager;
/** @var array */
protected $commonReadMessages = [];
@@ -127,7 +133,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 +154,7 @@ class RoomController extends AEnvironmentAwareController {
$this->l10n = $l10n;
$this->config = $config;
$this->talkConfig = $talkConfig;
+ $this->cloudIdManager = $cloudIdManager;
}
protected function getTalkHashHeader(): array {
@@ -1029,9 +1037,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 +1095,21 @@ class RoomController extends AEnvironmentAwareController {
$this->guestManager->sendEmailInvitation($this->room, $participant);
return new DataResponse($data);
+ } elseif ($source === 'remote') {
+ if (!$this->federationManager->isEnabled()) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+ 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(),
+ 'displayName' => $newUser->getDisplayId(),
+ ];
} else {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
@@ -1092,6 +1118,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 +1132,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/Exceptions/RoomHasNoModeratorException.php b/lib/Exceptions/RoomHasNoModeratorException.php
new file mode 100644
index 000000000..7b9087dbe
--- /dev/null
+++ b/lib/Exceptions/RoomHasNoModeratorException.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 RoomHasNoModeratorException 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..c3fd88c10 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,10 @@ use OCP\IUser;
* FederationManager handles incoming federated rooms
*/
class FederationManager {
+ public const TALK_ROOM_RESOURCE = 'talk-room';
+ public const TALK_PROTOCOL_NAME = 'nctalk';
+ public const TOKEN_LENGTH = 15;
+
/** @var IConfig */
private $config;
@@ -59,16 +65,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;
}
/**
@@ -77,11 +88,12 @@ class FederationManager {
*/
public function isEnabled(): bool {
// TODO: Set to default true once implementation is complete
- return $this->config->getAppValue(Application::APP_ID, 'federation_enabled', "false") === "true";
+ return $this->config->getAppValue(Application::APP_ID, 'federation_enabled', 'false') === 'true';
}
/**
* @param IUser $user
+ * @param string $remoteId
* @param int $roomType
* @param string $roomName
* @param string $roomToken
@@ -110,6 +122,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 +133,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 +149,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..465538d00
--- /dev/null
+++ b/lib/Federation/Notifications.php
@@ -0,0 +1,244 @@
+<?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 OCA\Talk\Exceptions\RoomHasNoModeratorException;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Room;
+use OCP\BackgroundJob\IJobList;
+use OCP\Federation\ICloudFederationFactory;
+use OCP\Federation\ICloudFederationNotification;
+use OCP\Federation\ICloudFederationProviderManager;
+use OCP\HintException;
+use OCP\IUser;
+use OCP\IUserManager;
+use Psr\Log\LoggerInterface;
+
+class Notifications {
+ /** @var ICloudFederationFactory */
+ private $cloudFederationFactory;
+
+ /** @var LoggerInterface */
+ private $logger;
+
+ /** @var ICloudFederationProviderManager */
+ private $federationProviderManager;
+
+ /** @var IJobList */
+ private $jobList;
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var AddressHandler */
+ private $addressHandler;
+
+ public function __construct(
+ ICloudFederationFactory $cloudFederationFactory,
+ AddressHandler $addressHandler,
+ LoggerInterface $logger,
+ ICloudFederationProviderManager $federationProviderManager,
+ IJobList $jobList,
+ IUserManager $userManager
+ ) {
+ $this->cloudFederationFactory = $cloudFederationFactory;
+ $this->logger = $logger;
+ $this->federationProviderManager = $federationProviderManager;
+ $this->jobList = $jobList;
+ $this->userManager = $userManager;
+ $this->addressHandler = $addressHandler;
+ }
+
+ /**
+ * @throws HintException
+ * @throws RoomHasNoModeratorException
+ * @throws \OCP\DB\Exception
+ */
+ public function sendRemoteShare(string $providerId, string $token, string $shareWith, string $sharedBy,
+ string $sharedByFederatedId, string $shareType, Room $room, Attendee $roomOwnerAttendee): bool {
+ [$user, $remote] = $this->addressHandler->splitUserRemote($shareWith);
+
+ $roomName = $room->getName();
+ $roomType = $room->getType();
+ $roomToken = $room->getToken();
+
+ if (!($user && $remote)) {
+ $this->logger->info(
+ "could not share $roomToken, invalid contact $shareWith",
+ ['app' => Application::APP_ID]
+ );
+ return false;
+ }
+
+ /** @var IUser|null $roomOwner */
+ $roomOwner = null;
+ if ($roomOwnerAttendee) {
+ $roomOwner = $this->userManager->get($roomOwnerAttendee->getActorId());
+ } else {
+ throw new RoomHasNoModeratorException();
+ }
+
+ $remote = $this->prepareRemoteUrl($remote);
+
+ $share = $this->cloudFederationFactory->getCloudFederationShare(
+ $user . '@' . $remote,
+ $roomToken,
+ '',
+ $providerId,
+ $roomOwner->getCloudId(),
+ $roomOwner->getDisplayName(),
+ $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'] = FederationManager::TALK_PROTOCOL_NAME;
+ $share->setProtocol($protocol);
+
+ $response = $this->federationProviderManager->sendShare($share);
+ if (is_array($response)) {
+ return true;
+ }
+ $this->logger->info(
+ "failed sharing $roomToken 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): void {
+ $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): void {
+ $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): void {
+ $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..89a9460d3 100644
--- a/lib/Model/AttendeeMapper.php
+++ b/lib/Model/AttendeeMapper.php
@@ -73,7 +73,25 @@ class AttendeeMapper extends QBMapper {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
- ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
+
+ return $this->findEntity($query);
+ }
+
+ /**
+ * @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('remote_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('access_token', $query->createNamedParameter($token, IQueryBuilder::PARAM_STR)));
return $this->findEntity($query);
}
@@ -100,6 +118,25 @@ class AttendeeMapper extends QBMapper {
/**
* @param int $roomId
+ * @param array $participantType
+ * @return Attendee[]
+ * @throws DBException
+ */
+ public function getActorsByParticipantTypes(int $roomId, array $participantType): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
+
+ if (!empty($participantType)) {
+ $query->andWhere($query->expr()->in('participant_type', $query->createNamedParameter($participantType, IQueryBuilder::PARAM_INT_ARRAY)));
+ }
+
+ return $this->findEntities($query);
+ }
+
+ /**
+ * @param int $roomId
* @param string $actorType
* @param int|null $lastJoinedCall
* @return int
diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php
index 6019c8cbf..90b5712c3 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,10 @@ class ParticipantService {
/**
* @param Room $room
* @param array $participants
+ * @param IUser|null $addedBy User that is attempting to add these users (must be set for federated users to be added)
+ * @throws \Exception thrown if $addedBy is not set when adding a federated user
*/
- 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 +330,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) {
+ throw new \Exception('$addedBy must be set to add a federated user');
+ }
+ $participant['accessToken'] = $this->secureRandom->generate(
+ FederationManager::TOKEN_LENGTH,
+ ISecureRandom::CHAR_HUMAN_READABLE
+ );
}
$attendee = new Attendee();
@@ -341,8 +357,12 @@ class ParticipantService {
$attendee->setLastReadMessage($lastMessage);
$attendee->setReadPrivacy($readPrivacy);
try {
- $this->attendeeMapper->insert($attendee);
+ $entity = $this->attendeeMapper->insert($attendee);
$attendees[] = $attendee;
+
+ if ($attendee->getActorType() === Attendee::ACTOR_FEDERATED_USERS) {
+ $this->notifications->sendRemoteShare((string) $entity->getId(), $participant['accessToken'], $participant['actorId'], $addedBy->getDisplayName(), $addedBy->getCloudId(), 'user', $room, $this->getHighestPermissionAttendee($room));
+ }
} catch (Exception $e) {
if ($e->getReason() !== Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
throw $e;
@@ -358,6 +378,30 @@ class ParticipantService {
}
}
+ public function getHighestPermissionAttendee(Room $room): ?Attendee {
+ try {
+ $roomOwners = $this->attendeeMapper->getActorsByParticipantTypes($room->getId(), [Participant::OWNER]);
+
+ if (!empty($roomOwners)) {
+ foreach ($roomOwners as $owner) {
+ if ($owner->getActorType() === Attendee::ACTOR_USERS) {
+ return $owner;
+ }
+ }
+ }
+ $roomModerators = $this->attendeeMapper->getActorsByParticipantTypes($room->getId(), [Participant::MODERATOR]);
+ if (!empty($roomOwners)) {
+ foreach ($roomModerators as $moderator) {
+ if ($moderator->getActorType() === Attendee::ACTOR_USERS) {
+ return $moderator;
+ }
+ }
+ }
+ } catch (Exception $e) {
+ }
+ return null;
+ }
+
/**
* @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;