Merge pull request #5775 from nextcloud/enh/5723/add-database

Implement Recieving Room Shares
This commit is contained in:
Joas Schilling 2021-07-16 09:38:31 +02:00 коммит произвёл GitHub
Родитель c4dc1af621 06dd757d48
Коммит 56e1d3db96
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 982 добавлений и 9 удалений

Просмотреть файл

@ -16,7 +16,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
]]></description>
<version>13.0.0-dev</version>
<version>13.0.0-dev.1</version>
<licence>agpl</licence>
<author>Daniel Calviño Sánchez</author>

Просмотреть файл

@ -523,6 +523,27 @@ return [
],
],
/**
* Federation
*/
[
'name' => 'Federation#acceptShare',
'url' => 'api/{apiVersion}/federation/invitation/{id}',
'verb' => 'POST',
'requirements' => [
'apiVersion' => 'v1',
],
],
[
'name' => 'Federation#rejectShare',
'url' => 'api/{apiVersion}/federation/invitation/{id}',
'verb' => 'DELETE',
'requirements' => [
'apiVersion' => 'v1',
],
],
/**
* PublicShareAuth
*/

Просмотреть файл

@ -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++;
}

Просмотреть файл

@ -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();
}
}

Просмотреть файл

@ -0,0 +1,265 @@
<?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 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\Room;
use OCA\Talk\Service\ParticipantService;
use OCP\AppFramework\Http;
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\HintException;
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 (!in_array($share->getShareType(), $this->getSupportedShareTypes(), true)) {
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('shareType is not a number', '', Http::STATUS_BAD_REQUEST);
}
$shareSecret = $share->getShareSecret();
$shareWith = $share->getShareWith();
$remoteId = $share->getProviderId();
$roomToken = $share->getResourceName();
$roomName = $share->getProtocol()['roomName'];
$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 && $remoteId && is_string($roomName) && $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, $remoteId, $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
}
return [];
// 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, Room::PARTICIPANT_LEFT);
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_USERS) {
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'];
}
}

Просмотреть файл

@ -0,0 +1,159 @@
<?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\AppInfo\Application;
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->getAppValue(Application::APP_ID, 'federation_enabled', "false") === "true";
}
/**
* @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, string $remoteId, 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->setRemoteId($remoteId);
$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(),
'remoteId' => $invitation->getRemoteId(),
]
];
$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);
}
}

Просмотреть файл

@ -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 $serverUrl = 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 ($serverUrl === null) {
$query->andWhere($query->expr()->isNull('r.server_url'));
} else {
$query->andWhere($query->expr()->eq('r.server_url', $query->createNamedParameter($serverUrl)));
}
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()]);

Просмотреть файл

@ -0,0 +1,102 @@
<?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();
$table = $schema->getTable('talk_attendees');
if (!$table->hasColumn('access_token')) {
$table->addColumn('access_token', Types::STRING, [
'notnull' => false,
'default' => null,
'length' => 64
]);
}
if (!$table->hasColumn('remote_id')) {
$table->addColumn('remote_id', Types::STRING, [
'notnull' => false,
'default' => null,
'length' => 255,
]);
}
$table = $schema->getTable('talk_rooms');
if (!$table->hasColumn('server_url')) {
$table->addColumn('server_url', Types::STRING, [
'notnull' => false,
'default' => null,
]);
}
if (!$schema->hasTable('talk_invitations')) {
$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,
'length' => 64,
]);
$table->addColumn('remote_id', Types::STRING, [
'notnull' => true,
'length' => 255,
]);
$table->setPrimaryKey(['id']);
$table->addIndex(['room_id']);
}
return $schema;
}
}

Просмотреть файл

@ -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,10 @@ 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()
* @method void setRemoteId(string $remoteId)
* @method string getRemoteId()
*/
class Attendee extends Entity {
public const ACTOR_USERS = 'users';
@ -59,6 +63,7 @@ class Attendee extends Entity {
public const ACTOR_EMAILS = 'emails';
public const ACTOR_CIRCLES = 'circles';
public const ACTOR_BRIDGED = 'bridged';
public const ACTOR_FEDERATED_USERS = 'federated_users';
public const PUBLISHING_PERMISSIONS_NONE = 0;
public const PUBLISHING_PERMISSIONS_AUDIO = 1;
@ -105,6 +110,12 @@ class Attendee extends Entity {
/** @var int */
protected $publishingPermissions;
/** @var string */
protected $accessToken;
/** @var string */
protected $remoteId;
public function __construct() {
$this->addType('roomId', 'int');
$this->addType('actorType', 'string');
@ -119,6 +130,8 @@ class Attendee extends Entity {
$this->addType('lastMentionMessage', 'int');
$this->addType('readPrivacy', 'int');
$this->addType('publishingPermissions', 'int');
$this->addType('accessToken', 'string');
$this->addType('remote_id', 'string');
}
public function getDisplayName(): string {
@ -144,6 +157,8 @@ class Attendee extends Entity {
'last_mention_message' => $this->getLastMentionMessage(),
'read_privacy' => $this->getReadPrivacy(),
'publishing_permissions' => $this->getPublishingPermissions(),
'access_token' => $this->getAccessToken(),
'remote_id' => $this->getRemoteId(),
];
}
}

Просмотреть файл

@ -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();
@ -57,6 +62,22 @@ class AttendeeMapper extends QBMapper {
return $this->findEntity($query);
}
/**
* @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
@ -153,6 +174,8 @@ 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'],
'remote_id' => (string) $row['remote_id'],
]);
}
}

73
lib/Model/Invitation.php Normal file
Просмотреть файл

@ -0,0 +1,73 @@
<?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()
* @method void setRemoteId(string $remoteId)
* @method string getRemoteId()
*/
class Invitation extends Entity {
/** @var int */
protected $roomId;
/** @var string */
protected $userId;
/** @var string */
protected $accessToken;
/** @var string */
protected $remoteId;
public function __construct() {
$this->addType('roomId', 'int');
$this->addType('userId', 'string');
$this->addType('accessToken', 'string');
$this->addType('remoteId', 'string');
}
public function asArray(): array {
return [
'id' => $this->getId(),
'room_id' => $this->getRoomId(),
'user_id' => $this->getUserId(),
'access_token' => $this->getAccessToken(),
'remote_id' => $this->getRemoteId(),
];
}
}

Просмотреть файл

@ -0,0 +1,106 @@
<?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' => (int) $row['room_id'],
'user_id' => (string) $row['user_id'],
'access_token' => (string) $row['access_token'],
'remote_id' => (string) $row['remote_id'],
]);
}
}

Просмотреть файл

@ -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,8 @@ class SelectHelper {
->addSelect($alias . 'last_mention_message')
->addSelect($alias . 'read_privacy')
->addSelect($alias . 'publishing_permissions')
->addSelect($alias . 'access_token')
->addSelect($alias . 'remote_id')
->selectAlias($alias . 'id', 'a_id');
}

Просмотреть файл

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace OCA\Talk\Notification;
use OCA\FederatedFileSharing\AddressHandler;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Config;
@ -36,6 +37,7 @@ use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCP\Comments\ICommentsManager;
use OCP\Comments\NotFoundException;
use OCP\HintException;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
@ -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');
}
@ -270,6 +280,47 @@ class Notifier implements INotifier {
return $temp;
}
/**
* @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');
$rosParameters = [
'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'],
]
];
$placeholders = $replacements = [];
foreach ($rosParameters as $placeholder => $parameter) {
$placeholders[] = '{' . $placeholder .'}';
$replacements[] = $parameter['name'];
}
$notification->setParsedMessage(str_replace($placeholders, $replacements, $message));
$notification->setRichMessage($message, $rosParameters);
return $notification;
}
/**
* @param INotification $notification
* @param Room $room

Просмотреть файл

@ -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;

Просмотреть файл

@ -326,6 +326,12 @@ class ParticipantService {
if (isset($participant['displayName'])) {
$attendee->setDisplayName($participant['displayName']);
}
if (isset($participant['accessToken'])) {
$attendee->setAccessToken($participant['accessToken']);
}
if (isset($participant['remoteId'])) {
$attendee->setRemoteId($participant['remoteId']);
}
$attendee->setParticipantType($participant['participantType'] ?? Participant::USER);
$attendee->setLastReadMessage($lastMessage);
$attendee->setReadPrivacy($readPrivacy);

Просмотреть файл

@ -28,6 +28,7 @@
<referencedClass name="OCA\Circles\Model\Circle" />
<referencedClass name="OCA\Circles\Model\Member" />
<referencedClass name="OCA\DAV\CardDAV\PhotoCache" />
<referencedClass name="OCA\FederatedFileSharing\AddressHandler" />
<referencedClass name="OCA\Files_Sharing\SharedStorage" />
</errorLevel>
</UndefinedClass>
@ -43,6 +44,7 @@
<referencedClass name="OC\DB\ConnectionAdapter" />
<referencedClass name="OCA\Circles\Model\Member" />
<referencedClass name="OCA\DAV\CardDAV\PhotoCache" />
<referencedClass name="OCA\FederatedFileSharing\AddressHandler" />
</errorLevel>
</UndefinedDocblockClass>
<UndefinedInterfaceMethod>

Просмотреть файл

@ -21,6 +21,7 @@
namespace OCA\Talk\Tests\php\Notifications;
use OCA\FederatedFileSharing\AddressHandler;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Config;
@ -75,6 +76,8 @@ class NotifierTest extends \Test\TestCase {
protected $definitions;
/** @var Notifier */
protected $notifier;
/** @var AddressHandler|MockObject */
protected $addressHandler;
public function setUp(): void {
parent::setUp();
@ -91,6 +94,7 @@ class NotifierTest extends \Test\TestCase {
$this->commentsManager = $this->createMock(CommentsManager::class);
$this->messageParser = $this->createMock(MessageParser::class);
$this->definitions = $this->createMock(Definitions::class);
$this->addressHandler = $this->createMock(AddressHandler::class);
$this->notifier = new Notifier(
$this->lFactory,
@ -104,7 +108,8 @@ class NotifierTest extends \Test\TestCase {
$this->notificationManager,
$this->commentsManager,
$this->messageParser,
$this->definitions
$this->definitions,
$this->addressHandler
);
}

Просмотреть файл

@ -70,6 +70,7 @@ class RoomTest extends TestCase {
'Test',
'description',
'passy',
'',
0,
Participant::FLAG_DISCONNECTED,
null,