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:
authorGary Kim <gary@garykim.dev>2021-06-11 06:35:54 +0300
committerGary Kim <gary@garykim.dev>2021-07-15 20:54:35 +0300
commita35b98f8c15e19bde6098081b41e544e95e4b532 (patch)
treea9af9784cd16ae1c4321c8c29e1ab8996db57196 /lib
parentddda9be34bd14f8ccf14521a6e100870493cd4b3 (diff)
Implement CloudFederationProvider for Talk
Signed-off-by: Gary Kim <gary@garykim.dev>
Diffstat (limited to 'lib')
-rw-r--r--lib/BackgroundJob/RemoveEmptyRooms.php6
-rw-r--r--lib/Controller/FederationController.php87
-rw-r--r--lib/Federation/CloudFederationProviderTalk.php262
-rw-r--r--lib/Federation/FederationManager.php156
-rw-r--r--lib/Manager.php44
-rw-r--r--lib/Migration/Version13000Date20210625232111.php88
-rw-r--r--lib/Model/Attendee.php10
-rw-r--r--lib/Model/AttendeeMapper.php24
-rw-r--r--lib/Model/Invitation.php66
-rw-r--r--lib/Model/InvitationMapper.php105
-rw-r--r--lib/Model/SelectHelper.php2
-rw-r--r--lib/Notification/Notifier.php50
-rw-r--r--lib/Room.php12
-rw-r--r--lib/Service/ParticipantService.php3
14 files changed, 908 insertions, 7 deletions
diff --git a/lib/BackgroundJob/RemoveEmptyRooms.php b/lib/BackgroundJob/RemoveEmptyRooms.php
index af9738809..3c620c171 100644
--- a/lib/BackgroundJob/RemoveEmptyRooms.php
+++ b/lib/BackgroundJob/RemoveEmptyRooms.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace OCA\Talk\BackgroundJob;
+use OCA\Talk\Federation\FederationManager;
use OCA\Talk\Service\ParticipantService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
@@ -46,6 +47,9 @@ class RemoveEmptyRooms extends TimedJob {
/** @var LoggerInterface */
protected $logger;
+ /** @var FederationManager */
+ protected $federationManager;
+
protected $numDeletedRooms = 0;
public function __construct(ITimeFactory $timeFactory,
@@ -77,7 +81,7 @@ class RemoveEmptyRooms extends TimedJob {
return;
}
- if ($this->participantService->getNumberOfActors($room) === 0 && $room->getObjectType() !== 'file') {
+ if ($this->participantService->getNumberOfActors($room) === 0 && $room->getObjectType() !== 'file' && $this->federationManager->getNumberOfInvitations($room) === 0) {
$room->deleteRoom();
$this->numDeletedRooms++;
}
diff --git a/lib/Controller/FederationController.php b/lib/Controller/FederationController.php
new file mode 100644
index 000000000..ea951d593
--- /dev/null
+++ b/lib/Controller/FederationController.php
@@ -0,0 +1,87 @@
+<?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\Controller;
+
+use OCA\Talk\AppInfo\Application;
+use OCA\Talk\Exceptions\UnauthorizedException;
+use OCA\Talk\Federation\FederationManager;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\DB\Exception as DBException;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
+
+class FederationController extends OCSController {
+ /** @var FederationManager */
+ private $federationManager;
+
+ /** @var IUserSession */
+ private $userSession;
+
+ public function __construct(IRequest $request, FederationManager $federationManager, IUserSession $userSession) {
+ parent::__construct(Application::APP_ID, $request);
+ $this->federationManager = $federationManager;
+ $this->userSession = $userSession;
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * @param int $id
+ * @return DataResponse
+ * @throws UnauthorizedException
+ * @throws DBException
+ * @throws MultipleObjectsReturnedException
+ */
+ public function acceptShare(int $id): DataResponse {
+ $user = $this->userSession->getUser();
+ if (!$user instanceof IUser) {
+ throw new UnauthorizedException();
+ }
+ $this->federationManager->acceptRemoteRoomShare($user, $id);
+ return new DataResponse();
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * @param int $id
+ * @return DataResponse
+ * @throws UnauthorizedException
+ * @throws DBException
+ * @throws MultipleObjectsReturnedException
+ */
+ public function rejectShare(int $id): DataResponse {
+ $user = $this->userSession->getUser();
+ if (!$user instanceof IUser) {
+ throw new UnauthorizedException();
+ }
+ $this->federationManager->rejectRemoteRoomShare($user, $id);
+ return new DataResponse();
+ }
+}
diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php
new file mode 100644
index 000000000..9cf401afd
--- /dev/null
+++ b/lib/Federation/CloudFederationProviderTalk.php
@@ -0,0 +1,262 @@
+<?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 Exception;
+use OC\AppFramework\Http;
+use OC\HintException;
+use OCA\FederatedFileSharing\AddressHandler;
+use OCA\Talk\AppInfo\Application;
+use OCA\Talk\Manager;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Model\AttendeeMapper;
+use OCA\Talk\Participant;
+use OCA\Talk\Service\ParticipantService;
+use OCP\DB\Exception as DBException;
+use OCP\Federation\Exceptions\ActionNotSupportedException;
+use OCP\Federation\Exceptions\AuthenticationFailedException;
+use OCP\Federation\Exceptions\BadRequestException;
+use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
+use OCP\Federation\ICloudFederationProvider;
+use OCP\Federation\ICloudFederationShare;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Notification\IManager as INotificationManager;
+use OCP\Share\Exceptions\ShareNotFound;
+
+class CloudFederationProviderTalk implements ICloudFederationProvider {
+
+ /** @var IUserManager */
+ private $userManager;
+
+ /** @var AddressHandler */
+ private $addressHandler;
+
+ /** @var FederationManager */
+ private $federationManager;
+
+ /** @var INotificationManager */
+ private $notificationManager;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var ParticipantService */
+ private $participantService;
+
+ /** @var AttendeeMapper */
+ private $attendeeMapper;
+
+ /** @var Manager */
+ private $manager;
+
+ public function __construct(
+ IUserManager $userManager,
+ AddressHandler $addressHandler,
+ FederationManager $federationManager,
+ INotificationManager $notificationManager,
+ IURLGenerator $urlGenerator,
+ ParticipantService $participantService,
+ AttendeeMapper $attendeeMapper,
+ Manager $manager
+ ) {
+ $this->userManager = $userManager;
+ $this->addressHandler = $addressHandler;
+ $this->federationManager = $federationManager;
+ $this->notificationManager = $notificationManager;
+ $this->urlGenerator = $urlGenerator;
+ $this->participantService = $participantService;
+ $this->attendeeMapper = $attendeeMapper;
+ $this->manager = $manager;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getShareType(): string {
+ return 'talk-room';
+ }
+
+ /**
+ * @inheritDoc
+ * @throws HintException
+ * @throws DBException
+ */
+ public function shareReceived(ICloudFederationShare $share): string {
+ if (!$this->federationManager->isEnabled()) {
+ throw new ProviderCouldNotAddShareException('Server does not support talk federation', '', Http::STATUS_SERVICE_UNAVAILABLE);
+ }
+ if ($share->getShareType() !== 'user') {
+ throw new ProviderCouldNotAddShareException('support for sharing with non-users not implemented yet', '', Http::STATUS_NOT_IMPLEMENTED);
+ // TODO: Implement group shares
+ }
+
+ if (!is_numeric($share->getShareType())) {
+ throw new ProviderCouldNotAddShareException('RoomType is not a number', '', Http::STATUS_BAD_REQUEST);
+ }
+
+ $shareSecret = $share->getShareSecret();
+ $shareWith = $share->getShareWith();
+ $roomToken = $share->getProviderId();
+ $roomName = $share->getResourceName();
+ $roomType = (int) $share->getShareType();
+ $sharedBy = $share->getSharedByDisplayName();
+ $sharedByFederatedId = $share->getSharedBy();
+ $owner = $share->getOwnerDisplayName();
+ $ownerFederatedId = $share->getOwner();
+ [, $remote] = $this->addressHandler->splitUserRemote($ownerFederatedId);
+
+ // if no explicit information about the person who created the share was send
+ // we assume that the share comes from the owner
+ if ($sharedByFederatedId === null) {
+ $sharedBy = $owner;
+ $sharedByFederatedId = $ownerFederatedId;
+ }
+
+ if ($remote && $shareSecret && $shareWith && $roomToken && $roomName && $owner) {
+ $shareWith = $this->userManager->get($shareWith);
+ if ($shareWith === null) {
+ throw new ProviderCouldNotAddShareException('User does not exist', '',Http::STATUS_BAD_REQUEST);
+ }
+
+ $shareId = (string) $this->federationManager->addRemoteRoom($shareWith, $roomType, $roomName, $roomToken, $remote, $shareSecret);
+
+ $this->notifyAboutNewShare($shareWith, $shareId, $sharedByFederatedId, $sharedBy, $roomName, $roomToken, $remote);
+ return $shareId;
+ }
+ throw new ProviderCouldNotAddShareException('required request data not found', '', Http::STATUS_BAD_REQUEST);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function notificationReceived($notificationType, $providerId, array $notification): array {
+ if (!is_numeric($providerId)) {
+ throw new BadRequestException(['providerId']);
+ }
+ switch ($notificationType) {
+ case 'SHARE_ACCEPTED':
+ return $this->shareAccepted((int) $providerId, $notification);
+ case 'SHARE_DECLINED':
+ return $this->shareDeclined((int) $providerId, $notification);
+ case 'SHARE_UNSHARED':
+ return []; // TODO: Implement
+ case 'REQUEST_RESHARE':
+ return []; // TODO: Implement
+ case 'RESHARE_UNDO':
+ return []; // TODO: Implement
+ case 'RESHARE_CHANGE_PERMISSION':
+ return []; // TODO: Implement
+ }
+ // TODO: Implement notificationReceived() method.
+ }
+
+ /**
+ * @throws ActionNotSupportedException
+ * @throws ShareNotFound
+ * @throws AuthenticationFailedException
+ */
+ private function shareAccepted(int $id, array $notification): array {
+ $attendee = $this->getAttendeeAndValidate($id, $notification['sharedSecret']);
+
+ // TODO: Add activity for share accepted
+
+ return [];
+ }
+
+ /**
+ * @throws ActionNotSupportedException
+ * @throws ShareNotFound
+ * @throws AuthenticationFailedException
+ */
+ private function shareDeclined(int $id, array $notification): array {
+ $attendee = $this->getAttendeeAndValidate($id, $notification['sharedSecret']);
+
+ $room = $this->manager->getRoomById($attendee->getRoomId());
+ $participant = new Participant($room, $attendee, null);
+ $this->participantService->removeAttendee($room, $participant, 'Left Room');
+ return [];
+ }
+
+ /**
+ * @throws AuthenticationFailedException
+ * @throws ActionNotSupportedException
+ * @throws ShareNotFound
+ */
+ private function getAttendeeAndValidate(int $id, string $sharedSecret): Attendee {
+ if (!$this->federationManager->isEnabled()) {
+ throw new ActionNotSupportedException('Server does not support Talk federation');
+ }
+
+ try {
+ $attendee = $this->attendeeMapper->getById($id);
+ } catch (Exception $ex) {
+ throw new ShareNotFound();
+ }
+ if ($attendee->getActorType() !== Attendee::ACTOR_FEDERATED_REMOTE_USER) {
+ throw new ShareNotFound();
+ }
+ if ($attendee->getAccessToken() !== $sharedSecret) {
+ throw new AuthenticationFailedException();
+ }
+ 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)
+ ->setUser($shareWith->getUID())
+ ->setDateTime(new \DateTime())
+ ->setObject('remote_talk_share', $shareId)
+ ->setSubject('remote_talk_share', [
+ 'sharedByDisplayName' => $sharedByName,
+ 'sharedByFederatedId' => $sharedByFederatedId,
+ 'roomName' => $roomName,
+ 'serverUrl' => $serverUrl,
+ 'roomToken' => $roomToken,
+ ]);
+
+ $declineAction = $notification->createAction();
+ $declineAction->setLabel('decline')
+ ->setLink($this->urlGenerator->linkToOCSRouteAbsolute('spreed.Federation.rejectShare', ['id' => $shareId]), 'DELETE');
+ $notification->addAction($declineAction);
+
+ $acceptAction = $notification->createAction();
+ $acceptAction->setLabel('accept')
+ ->setLink($this->urlGenerator->linkToOCSRouteAbsolute('spreed.Federation.acceptShare', ['id' => $shareId]), 'POST');
+ $notification->addAction($acceptAction);
+
+ $this->notificationManager->notify($notification);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSupportedShareTypes() {
+ return ['user'];
+ }
+}
diff --git a/lib/Federation/FederationManager.php b/lib/Federation/FederationManager.php
new file mode 100644
index 000000000..58336478c
--- /dev/null
+++ b/lib/Federation/FederationManager.php
@@ -0,0 +1,156 @@
+<?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\Talk\Exceptions\RoomNotFoundException;
+use OCA\Talk\Exceptions\UnauthorizedException;
+use OCA\Talk\Manager;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Model\Invitation;
+use OCA\Talk\Model\InvitationMapper;
+use OCA\Talk\Room;
+use OCA\Talk\Service\ParticipantService;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\DB\Exception as DBException;
+use OCP\IConfig;
+use OCP\IUser;
+
+/**
+ * Class FederationManager
+ *
+ * @package OCA\Talk\Federation
+ *
+ * FederationManager handles incoming federated rooms
+ */
+class FederationManager {
+ /** @var IConfig */
+ private $config;
+
+ /** @var Manager */
+ private $manager;
+
+ /** @var ParticipantService */
+ private $participantService;
+
+ /** @var InvitationMapper */
+ private $invitationMapper;
+
+ public function __construct(
+ IConfig $config,
+ Manager $manager,
+ ParticipantService $participantService,
+ InvitationMapper $invitationMapper
+ ) {
+ $this->config = $config;
+ $this->manager = $manager;
+ $this->participantService = $participantService;
+ $this->invitationMapper = $invitationMapper;
+ }
+
+ /**
+ * Determine if Talk federation is enabled on this instance
+ * @return bool
+ */
+ public function isEnabled(): bool {
+ // TODO: Set to default true once implementation is complete
+ return $this->config->getSystemValueBool('talk_federation_enabled', false);
+ }
+
+ /**
+ * @param IUser $user
+ * @param int $roomType
+ * @param string $roomName
+ * @param string $roomToken
+ * @param string $remoteUrl
+ * @param string $sharedSecret
+ * @return int share id for this specific remote room share
+ * @throws DBException
+ */
+ public function addRemoteRoom(IUser $user, int $roomType, string $roomName, string $roomToken, string $remoteUrl, string $sharedSecret): int {
+ try {
+ $room = $this->manager->getRoomByToken($roomToken, null, $remoteUrl);
+ } catch (RoomNotFoundException $ex) {
+ $room = $this->manager->createRemoteRoom($roomType, $roomName, $roomToken, $remoteUrl);
+ }
+ $invitation = new Invitation();
+ $invitation->setUserId($user->getUID());
+ $invitation->setRoomId($room->getId());
+ $invitation->setAccessToken($sharedSecret);
+ $invitation = $this->invitationMapper->insert($invitation);
+
+ return $invitation->getId();
+ }
+
+ /**
+ * @throws DBException
+ * @throws UnauthorizedException
+ * @throws MultipleObjectsReturnedException
+ */
+ public function acceptRemoteRoomShare(IUser $user, int $shareId) {
+ $invitation = $this->invitationMapper->getInvitationById($shareId);
+ if ($invitation->getUserId() !== $user->getUID()) {
+ throw new UnauthorizedException('invitation is for a different user');
+ }
+
+ // Add user to the room
+ $room = $this->manager->getRoomById($invitation->getRoomId());
+ $participant = [
+ [
+ 'actorType' => Attendee::ACTOR_USERS,
+ 'actorId' => $user->getUID(),
+ 'displayName' => $user->getDisplayName(),
+ 'accessToken' => $invitation->getAccessToken(),
+ ]
+ ];
+ $this->participantService->addUsers($room, $participant);
+
+ $this->invitationMapper->delete($invitation);
+
+ // TODO: Send SHARE_ACCEPTED notification
+ }
+
+ /**
+ * @throws DBException
+ * @throws UnauthorizedException
+ * @throws MultipleObjectsReturnedException
+ */
+ 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');
+ }
+ $this->invitationMapper->delete($invitation);
+
+ // TODO: Send SHARE_DECLINED notification
+ }
+
+ /**
+ * @throws DBException
+ */
+ public function getNumberOfInvitations(Room $room): int {
+ return $this->invitationMapper->countInvitationsForRoom($room);
+ }
+}
diff --git a/lib/Manager.php b/lib/Manager.php
index aeb52b200..044c6d3e3 100644
--- a/lib/Manager.php
+++ b/lib/Manager.php
@@ -37,6 +37,7 @@ use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
use OCP\Comments\NotFoundException;
+use OCP\DB\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICache;
@@ -185,6 +186,7 @@ class Manager {
(string) $row['name'],
(string) $row['description'],
(string) $row['password'],
+ (string) $row['server_url'],
(int) $row['active_guests'],
(int) $row['call_flag'],
$activeSince,
@@ -628,7 +630,7 @@ class Manager {
* @return Room
* @throws RoomNotFoundException
*/
- public function getRoomByActor(string $token, string $actorType, string $actorId, ?string $sessionId = null): Room {
+ public function getRoomByActor(string $token, string $actorType, string $actorId, ?string $sessionId = null, ?string $server_url = null): Room {
$query = $this->db->getQueryBuilder();
$helper = new SelectHelper();
$helper->selectRoomsTable($query);
@@ -641,6 +643,12 @@ class Manager {
))
->where($query->expr()->eq('r.token', $query->createNamedParameter($token)));
+ if ($server_url === null) {
+ $query->andWhere($query->expr()->isNull('r.server_url'));
+ } else {
+ $query->andWhere($query->expr()->eq('r.server_url', $query->createNamedParameter($server_url)));
+ }
+
if ($sessionId !== null) {
$helper->selectSessionsTable($query);
$query->leftJoin('a', 'talk_sessions', 's', $query->expr()->andX(
@@ -676,10 +684,10 @@ class Manager {
* @return Room
* @throws RoomNotFoundException
*/
- public function getRoomByToken(string $token, ?string $preloadUserId = null): Room {
+ public function getRoomByToken(string $token, ?string $preloadUserId = null, ?string $serverUrl = null): Room {
$preloadUserId = $preloadUserId === '' ? null : $preloadUserId;
if ($preloadUserId !== null) {
- return $this->getRoomByActor($token, Attendee::ACTOR_USERS, $preloadUserId);
+ return $this->getRoomByActor($token, Attendee::ACTOR_USERS, $preloadUserId, null, $serverUrl);
}
$query = $this->db->getQueryBuilder();
@@ -688,6 +696,13 @@ class Manager {
$query->from('talk_rooms', 'r')
->where($query->expr()->eq('r.token', $query->createNamedParameter($token)));
+ if ($serverUrl === null) {
+ $query->andWhere($query->expr()->isNull('r.server_url'));
+ } else {
+ $query->andWhere($query->expr()->eq('r.server_url', $query->createNamedParameter($serverUrl)));
+ }
+
+
$result = $query->execute();
$row = $result->fetch();
$result->closeCursor();
@@ -908,6 +923,29 @@ class Manager {
return $room;
}
+ /**
+ * @param int $type
+ * @param string $name
+ * @return Room
+ * @throws DBException
+ */
+ public function createRemoteRoom(int $type, string $name, string $token, string $serverUrl): Room {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->insert('talk_rooms')
+ ->values([
+ 'name' => $qb->createNamedParameter($name),
+ 'type' => $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT),
+ 'token' => $qb->createNamedParameter($token),
+ 'server_url' => $qb->createNamedParameter($serverUrl),
+ ]);
+
+ $qb->executeStatement();
+ $roomId = $qb->getLastInsertId();
+
+ return $this->getRoomById($roomId);
+ }
+
public function resolveRoomDisplayName(Room $room, string $userId): string {
if ($room->getObjectType() === 'share:password') {
return $this->l->t('Password request: %s', [$room->getName()]);
diff --git a/lib/Migration/Version13000Date20210625232111.php b/lib/Migration/Version13000Date20210625232111.php
new file mode 100644
index 000000000..437a785d7
--- /dev/null
+++ b/lib/Migration/Version13000Date20210625232111.php
@@ -0,0 +1,88 @@
+<?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\Migration;
+
+use Closure;
+use Doctrine\DBAL\Schema\SchemaException;
+use Doctrine\DBAL\Types\Types;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version13000Date20210625232111 extends SimpleMigrationStep {
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ * @return null|ISchemaWrapper
+ * @throws SchemaException
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if ($schema->hasTable('talk_attendees')) {
+ $table = $schema->getTable('talk_attendees');
+ if (!$table->hasColumn('access_token')) {
+ $table->addColumn('access_token', Types::STRING, [
+ 'notnull' => false,
+ 'default' => null,
+ ]);
+ }
+ }
+
+ if ($schema->hasTable('talk_rooms')) {
+ $table = $schema->getTable('talk_rooms');
+ if (!$table->hasColumn('server_url')) {
+ $table->addColumn('server_url', Types::STRING, [
+ 'notnull' => false,
+ 'default' => null,
+ ]);
+ }
+ }
+
+ $table = $schema->createTable('talk_invitations');
+ $table->addColumn('id', Types::BIGINT, [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ ]);
+ $table->addColumn('room_id', Types::BIGINT, [
+ 'notnull' => true,
+ 'unsigned' => true,
+ ]);
+ $table->addColumn('user_id', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 255,
+ ]);
+ $table->addColumn('access_token', Types::STRING, [
+ 'notnull' => true,
+ ]);
+
+ $table->setPrimaryKey(['id']);
+
+ return $schema;
+ }
+}
diff --git a/lib/Model/Attendee.php b/lib/Model/Attendee.php
index 6f7b083a5..0591d1dac 100644
--- a/lib/Model/Attendee.php
+++ b/lib/Model/Attendee.php
@@ -27,7 +27,7 @@ use OCP\AppFramework\Db\Entity;
/**
* @method void setRoomId(int $roomId)
- * @method string getRoomId()
+ * @method int getRoomId()
* @method void setActorType(string $actorType)
* @method string getActorType()
* @method void setActorId(string $actorId)
@@ -51,6 +51,8 @@ use OCP\AppFramework\Db\Entity;
* @method int getReadPrivacy()
* @method void setPublishingPermissions(int $publishingPermissions)
* @method int getPublishingPermissions()
+ * @method void setAccessToken(string $accessToken)
+ * @method null|string getAccessToken()
*/
class Attendee extends Entity {
public const ACTOR_USERS = 'users';
@@ -59,6 +61,7 @@ class Attendee extends Entity {
public const ACTOR_EMAILS = 'emails';
public const ACTOR_CIRCLES = 'circles';
public const ACTOR_BRIDGED = 'bridged';
+ public const ACTOR_FEDERATED_REMOTE_USER = 'federated_remote';
public const PUBLISHING_PERMISSIONS_NONE = 0;
public const PUBLISHING_PERMISSIONS_AUDIO = 1;
@@ -105,6 +108,9 @@ class Attendee extends Entity {
/** @var int */
protected $publishingPermissions;
+ /** @var string */
+ protected $accessToken;
+
public function __construct() {
$this->addType('roomId', 'int');
$this->addType('actorType', 'string');
@@ -119,6 +125,7 @@ class Attendee extends Entity {
$this->addType('lastMentionMessage', 'int');
$this->addType('readPrivacy', 'int');
$this->addType('publishingPermissions', 'int');
+ $this->addType('accessToken', 'string');
}
public function getDisplayName(): string {
@@ -144,6 +151,7 @@ class Attendee extends Entity {
'last_mention_message' => $this->getLastMentionMessage(),
'read_privacy' => $this->getReadPrivacy(),
'publishing_permissions' => $this->getPublishingPermissions(),
+ 'access_token' => $this->getAccessToken(),
];
}
}
diff --git a/lib/Model/AttendeeMapper.php b/lib/Model/AttendeeMapper.php
index 3e8586389..cbf6f0393 100644
--- a/lib/Model/AttendeeMapper.php
+++ b/lib/Model/AttendeeMapper.php
@@ -23,12 +23,17 @@ declare(strict_types=1);
namespace OCA\Talk\Model;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
+use OCP\DB\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @method Attendee mapRowToEntity(array $row)
+ * @method Attendee findEntity(IQueryBuilder $query)
+ * @method Attendee[] findEntities(IQueryBuilder $query)
*/
class AttendeeMapper extends QBMapper {
@@ -44,7 +49,7 @@ class AttendeeMapper extends QBMapper {
* @param string $actorType
* @param string $actorId
* @return Attendee
- * @throws \OCP\AppFramework\Db\DoesNotExistException
+ * @throws DoesNotExistException
*/
public function findByActor(int $roomId, string $actorType, string $actorId): Attendee {
$query = $this->db->getQueryBuilder();
@@ -58,6 +63,22 @@ class AttendeeMapper extends QBMapper {
}
/**
+ * @param int $id
+ * @return Attendee
+ * @throws DoesNotExistException
+ * @throws MultipleObjectsReturnedException
+ * @throws DBException
+ */
+ public function getById(int $id): Attendee {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+
+ return $this->findEntity($query);
+ }
+
+ /**
* @param int $roomId
* @param string $actorType
* @param int|null $lastJoinedCall
@@ -153,6 +174,7 @@ class AttendeeMapper extends QBMapper {
'last_mention_message' => (int) $row['last_mention_message'],
'read_privacy' => (int) $row['read_privacy'],
'publishing_permissions' => (int) $row['publishing_permissions'],
+ 'access_token' => (string) $row['access_token'],
]);
}
}
diff --git a/lib/Model/Invitation.php b/lib/Model/Invitation.php
new file mode 100644
index 000000000..cf2a591e0
--- /dev/null
+++ b/lib/Model/Invitation.php
@@ -0,0 +1,66 @@
+<?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\Model;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * Class Invitation
+ *
+ * @package OCA\Talk\Model
+ *
+ * @method void setRoomId(int $roomId)
+ * @method int getRoomId()
+ * @method void setUserId(string $userId)
+ * @method string getUserId()
+ * @method void setAccessToken(string $accessToken)
+ * @method string getAccessToken()
+ */
+class Invitation extends Entity {
+ /** @var int */
+ protected $roomId;
+
+ /** @var string */
+ protected $userId;
+
+ /** @var string */
+ protected $accessToken;
+
+ public function __construct() {
+ $this->addType('roomId', 'int');
+ $this->addType('userId', 'string');
+ $this->addType('accessToken', 'string');
+ }
+
+ public function asArray(): array {
+ return [
+ 'id' => $this->getId(),
+ 'room_id' => $this->getRoomId(),
+ 'user_id' => $this->getUserId(),
+ 'access_token' => $this->getAccessToken(),
+ ];
+ }
+}
diff --git a/lib/Model/InvitationMapper.php b/lib/Model/InvitationMapper.php
new file mode 100644
index 000000000..2a453a2a0
--- /dev/null
+++ b/lib/Model/InvitationMapper.php
@@ -0,0 +1,105 @@
+<?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\Model;
+
+use OCA\Talk\Room;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\MultipleObjectsReturnedException;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\DB\Exception as DBException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * Class InvitationMapper
+ *
+ * @package OCA\Talk\Model
+ *
+ * @method Invitation mapRowToEntity(array $row)
+ * @method Invitation findEntity(IQueryBuilder $query)
+ * @method Invitation[] findEntities(IQueryBuilder $query)
+ */
+class InvitationMapper extends QBMapper {
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, 'talk_invitations', Invitation::class);
+ }
+
+ /**
+ * @throws DBException
+ * @throws MultipleObjectsReturnedException
+ * @throws DoesNotExistException
+ */
+ public function getInvitationById(int $id): Invitation {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
+
+ return $this->findEntity($qb);
+ }
+
+ /**
+ * @param Room $room
+ * @return Invitation[]
+ * @throws DBException
+ */
+ public function getInvitationsForRoom(Room $room): array {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->select('*')
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('room_id', $qb->createNamedParameter($room->getId())));
+
+ return $this->findEntities($qb);
+ }
+
+ /**
+ * @throws DBException
+ */
+ public function countInvitationsForRoom(Room $room): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->select($qb->func()->count('*', 'num_invitations'))
+ ->from($this->getTableName())
+ ->where($qb->expr()->eq('room_id', $qb->createNamedParameter($room->getId())));
+
+ $result = $qb->executeQuery();
+ $row = $result->fetch();
+ $result->closeCursor();
+
+ return (int) ($row['num_invitations' ?? 0]);
+ }
+
+ public function createInvitationFromRow(array $row): Invitation {
+ return $this->mapRowToEntity([
+ 'id' => $row['id'],
+ 'room_id' => $row['room_id'],
+ 'user_id' => $row['user_id'],
+ 'access_token' => $row['access_token'],
+ ]);
+ }
+}
diff --git a/lib/Model/SelectHelper.php b/lib/Model/SelectHelper.php
index dfcab988a..0730329fd 100644
--- a/lib/Model/SelectHelper.php
+++ b/lib/Model/SelectHelper.php
@@ -49,6 +49,7 @@ class SelectHelper {
->addSelect($alias . 'object_type')
->addSelect($alias . 'object_id')
->addSelect($alias . 'listable')
+ ->addSelect($alias . 'server_url')
->selectAlias($alias . 'id', 'r_id');
}
@@ -70,6 +71,7 @@ class SelectHelper {
->addSelect($alias . 'last_mention_message')
->addSelect($alias . 'read_privacy')
->addSelect($alias . 'publishing_permissions')
+ ->addSelect($alias . 'access_token')
->selectAlias($alias . 'id', 'a_id');
}
diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php
index eeb657bdf..ab81dcf0f 100644
--- a/lib/Notification/Notifier.php
+++ b/lib/Notification/Notifier.php
@@ -23,6 +23,8 @@ declare(strict_types=1);
namespace OCA\Talk\Notification;
+use OC\HintException;
+use OCA\FederatedFileSharing\AddressHandler;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Config;
@@ -77,6 +79,8 @@ class Notifier implements INotifier {
protected $messageParser;
/** @var Definitions */
protected $definitions;
+ /** @var AddressHandler */
+ protected $addressHandler;
/** @var Room[] */
protected $rooms = [];
@@ -94,7 +98,8 @@ class Notifier implements INotifier {
INotificationManager $notificationManager,
CommentsManager $commentManager,
MessageParser $messageParser,
- Definitions $definitions) {
+ Definitions $definitions,
+ AddressHandler $addressHandler) {
$this->lFactory = $lFactory;
$this->url = $url;
$this->config = $config;
@@ -107,6 +112,7 @@ class Notifier implements INotifier {
$this->commentManager = $commentManager;
$this->messageParser = $messageParser;
$this->definitions = $definitions;
+ $this->addressHandler = $addressHandler;
}
/**
@@ -258,6 +264,10 @@ class Notifier implements INotifier {
return $this->parseChatMessage($notification, $room, $participant, $l);
}
+ if ($subject === 'remote_talk_share') {
+ return $this->parseRemoteInvitationMessage($notification, $l);
+ }
+
$this->notificationManager->markProcessed($notification);
throw new \InvalidArgumentException('Unknown subject');
}
@@ -271,6 +281,44 @@ class Notifier implements INotifier {
}
/**
+ * @throws HintException
+ */
+ protected function parseRemoteInvitationMessage(INotification $notification, IL10N $l): INotification {
+ $subjectParameters = $notification->getSubjectParameters();
+
+ [$sharedById, $sharedByServer] = $this->addressHandler->splitUserRemote($subjectParameters['sharedByFederatedId']);
+
+ $message = $l->t('{user1} shared room {roomName} on {remoteServer} with you');
+ $parsedMessage = $l->t('{user1} shared room {roomName} on {remoteServer} with you', [
+ 'user1' => $subjectParameters['sharedByFederatedId'],
+ 'roomName' => $subjectParameters['roomName'],
+ 'remoteServer' => $subjectParameters['serverUrl'],
+ ]);
+
+ $notification->setParsedMessage($parsedMessage);
+ $notification->setRichMessage($message, [
+ 'user1' => [
+ 'type' => 'user',
+ 'id' => $sharedById,
+ 'name' => $subjectParameters['sharedByDisplayName'],
+ 'server' => $sharedByServer,
+ ],
+ 'roomName' => [
+ 'type' => 'highlight',
+ 'id' => $subjectParameters['serverUrl'] . '::' . $subjectParameters['roomToken'],
+ 'name' => $subjectParameters['roomName'],
+ ],
+ 'remoteServer' => [
+ 'type' => 'highlight',
+ 'id' => $subjectParameters['serverUrl'],
+ 'name' => $subjectParameters['serverUrl'],
+ ]
+ ]);
+
+ return $notification;
+ }
+
+ /**
* @param INotification $notification
* @param Room $room
* @param Participant $participant
diff --git a/lib/Room.php b/lib/Room.php
index d52fd2200..035072261 100644
--- a/lib/Room.php
+++ b/lib/Room.php
@@ -180,6 +180,8 @@ class Room {
private $description;
/** @var string */
private $password;
+ /** @var string */
+ private $serverUrl;
/** @var int */
private $activeGuests;
/** @var int */
@@ -218,6 +220,7 @@ class Room {
string $name,
string $description,
string $password,
+ string $serverUrl,
int $activeGuests,
int $callFlag,
?\DateTime $activeSince,
@@ -243,6 +246,7 @@ class Room {
$this->name = $name;
$this->description = $description;
$this->password = $password;
+ $this->serverUrl = $serverUrl;
$this->activeGuests = $activeGuests;
$this->callFlag = $callFlag;
$this->activeSince = $activeSince;
@@ -377,6 +381,14 @@ class Room {
return $this->password;
}
+ public function getServerUrl(): string {
+ return $this->serverUrl;
+ }
+
+ public function isFederatedRemoteRoom(): bool {
+ return $this->serverUrl !== '';
+ }
+
public function setParticipant(?string $userId, Participant $participant): void {
$this->currentUser = $userId;
$this->participant = $participant;
diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php
index be27b16ed..18e2698f1 100644
--- a/lib/Service/ParticipantService.php
+++ b/lib/Service/ParticipantService.php
@@ -326,6 +326,9 @@ class ParticipantService {
if (isset($participant['displayName'])) {
$attendee->setDisplayName($participant['displayName']);
}
+ if (isset($participant['accessToken'])) {
+ $attendee->setAccessToken($participant['accessToken']);
+ }
$attendee->setParticipantType($participant['participantType'] ?? Participant::USER);
$attendee->setLastReadMessage($lastMessage);
$attendee->setReadPrivacy($readPrivacy);