You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
7.7 KiB
248 lines
7.7 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
/**
|
|
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
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;
|
|
use OCA\Talk\Model\Attendee;
|
|
use OCA\Talk\Model\Invitation;
|
|
use OCA\Talk\Model\InvitationMapper;
|
|
use OCA\Talk\Participant;
|
|
use OCA\Talk\Room;
|
|
use OCA\Talk\Service\ParticipantService;
|
|
use OCP\AppFramework\Db\DoesNotExistException;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
|
|
use OCP\Federation\ICloudId;
|
|
use OCP\Federation\ICloudIdManager;
|
|
use OCP\IUser;
|
|
use OCP\Notification\IManager;
|
|
use SensitiveParameter;
|
|
|
|
/**
|
|
* Class FederationManager
|
|
*
|
|
* @package OCA\Talk\Federation
|
|
*
|
|
* FederationManager handles incoming federated rooms
|
|
*/
|
|
class FederationManager {
|
|
public const OCM_RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND';
|
|
public const TALK_ROOM_RESOURCE = 'talk-room';
|
|
public const TALK_PROTOCOL_NAME = 'nctalk';
|
|
public const NOTIFICATION_SHARE_ACCEPTED = 'SHARE_ACCEPTED';
|
|
public const NOTIFICATION_SHARE_DECLINED = 'SHARE_DECLINED';
|
|
public const NOTIFICATION_SHARE_UNSHARED = 'SHARE_UNSHARED';
|
|
public const NOTIFICATION_ROOM_MODIFIED = 'ROOM_MODIFIED';
|
|
public const NOTIFICATION_MESSAGE_POSTED = 'MESSAGE_POSTED';
|
|
public const TOKEN_LENGTH = 64;
|
|
|
|
public function __construct(
|
|
private Manager $manager,
|
|
private ParticipantService $participantService,
|
|
private InvitationMapper $invitationMapper,
|
|
private BackendNotifier $backendNotifier,
|
|
private IManager $notificationManager,
|
|
private ICloudIdManager $cloudIdManager,
|
|
private RestrictionValidator $restrictionValidator,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Check if $sharedBy is allowed to invite $shareWith
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
public function isAllowedToInvite(
|
|
IUser $user,
|
|
ICloudId $cloudIdToInvite,
|
|
): void {
|
|
$this->restrictionValidator->isAllowedToInvite($user, $cloudIdToInvite);
|
|
}
|
|
|
|
public function addRemoteRoom(
|
|
IUser $user,
|
|
int $remoteAttendeeId,
|
|
int $roomType,
|
|
string $roomName,
|
|
string $remoteToken,
|
|
string $remoteServerUrl,
|
|
#[SensitiveParameter]
|
|
string $sharedSecret,
|
|
string $inviterCloudId,
|
|
string $inviterDisplayName,
|
|
string $localCloudId,
|
|
): Invitation {
|
|
$couldHaveInviteWithOtherCasing = false;
|
|
try {
|
|
$room = $this->manager->getRoomByToken($remoteToken, null, $remoteServerUrl);
|
|
$couldHaveInviteWithOtherCasing = true;
|
|
} catch (RoomNotFoundException) {
|
|
$room = $this->manager->createRemoteRoom($roomType, $roomName, $remoteToken, $remoteServerUrl);
|
|
}
|
|
|
|
if ($couldHaveInviteWithOtherCasing) {
|
|
try {
|
|
$this->invitationMapper->getInvitationForUserByLocalRoom($room, $user->getUID(), true);
|
|
throw new ProviderCouldNotAddShareException('User already invited', '', Http::STATUS_BAD_REQUEST);
|
|
} catch (DoesNotExistException) {
|
|
// Not invited with any casing already, so all good.
|
|
}
|
|
}
|
|
|
|
$invitation = new Invitation();
|
|
$invitation->setUserId($user->getUID());
|
|
$invitation->setState(Invitation::STATE_PENDING);
|
|
$invitation->setLocalRoomId($room->getId());
|
|
$invitation->setLocalCloudId($localCloudId);
|
|
$invitation->setAccessToken($sharedSecret);
|
|
$invitation->setRemoteServerUrl($remoteServerUrl);
|
|
$invitation->setRemoteToken($remoteToken);
|
|
$invitation->setRemoteAttendeeId($remoteAttendeeId);
|
|
$invitation->setInviterCloudId($inviterCloudId);
|
|
$invitation->setInviterDisplayName($inviterDisplayName);
|
|
$this->invitationMapper->insert($invitation);
|
|
|
|
return $invitation;
|
|
}
|
|
|
|
protected function markNotificationProcessed(string $userId, int $shareId): void {
|
|
$notification = $this->notificationManager->createNotification();
|
|
$notification->setApp(Application::APP_ID)
|
|
->setUser($userId)
|
|
->setObject('remote_talk_share', (string) $shareId);
|
|
$this->notificationManager->markProcessed($notification);
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
* @throws CannotReachRemoteException
|
|
*/
|
|
public function acceptRemoteRoomShare(IUser $user, int $shareId): Participant {
|
|
try {
|
|
$invitation = $this->invitationMapper->getInvitationById($shareId);
|
|
} catch (DoesNotExistException $e) {
|
|
throw new \InvalidArgumentException('invitation');
|
|
}
|
|
if ($invitation->getUserId() !== $user->getUID()) {
|
|
throw new UnauthorizedException('user');
|
|
}
|
|
|
|
if ($invitation->getState() === Invitation::STATE_ACCEPTED) {
|
|
throw new \InvalidArgumentException('state');
|
|
}
|
|
|
|
|
|
$cloudId = $this->cloudIdManager->getCloudId($user->getUID(), null);
|
|
|
|
// Add user to the room
|
|
$room = $this->manager->getRoomById($invitation->getLocalRoomId());
|
|
if (
|
|
!$this->backendNotifier->sendShareAccepted($invitation->getRemoteServerUrl(), $invitation->getRemoteAttendeeId(), $invitation->getAccessToken(), $user->getDisplayName(), $cloudId->getId())
|
|
) {
|
|
throw new CannotReachRemoteException();
|
|
}
|
|
|
|
$participant = [
|
|
[
|
|
'actorType' => Attendee::ACTOR_USERS,
|
|
'actorId' => $user->getUID(),
|
|
'displayName' => $user->getDisplayName(),
|
|
'accessToken' => $invitation->getAccessToken(),
|
|
'remoteId' => $invitation->getRemoteAttendeeId(),
|
|
'invitedCloudId' => $invitation->getLocalCloudId(),
|
|
'lastReadMessage' => $room->getLastMessageId(),
|
|
]
|
|
];
|
|
$attendees = $this->participantService->addUsers($room, $participant, $user);
|
|
/** @var Attendee $attendee */
|
|
$attendee = array_pop($attendees);
|
|
|
|
$invitation->setState(Invitation::STATE_ACCEPTED);
|
|
$invitation->setLocalCloudId($cloudId->getId());
|
|
$this->invitationMapper->update($invitation);
|
|
|
|
$this->markNotificationProcessed($user->getUID(), $shareId);
|
|
|
|
return new Participant($room, $attendee, null);
|
|
}
|
|
|
|
/**
|
|
* @throws DoesNotExistException
|
|
*/
|
|
public function getRemoteShareById(int $shareId): Invitation {
|
|
return $this->invitationMapper->getInvitationById($shareId);
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
* @throws UnauthorizedException
|
|
*/
|
|
public function rejectRemoteRoomShare(IUser $user, int $shareId): void {
|
|
try {
|
|
$invitation = $this->invitationMapper->getInvitationById($shareId);
|
|
} catch (DoesNotExistException $e) {
|
|
throw new \InvalidArgumentException('invitation');
|
|
}
|
|
|
|
if ($invitation->getUserId() !== $user->getUID()) {
|
|
throw new UnauthorizedException('user');
|
|
}
|
|
|
|
if ($invitation->getState() !== Invitation::STATE_PENDING) {
|
|
throw new \InvalidArgumentException('state');
|
|
}
|
|
|
|
$this->rejectInvitation($invitation, $user->getUID());
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
* @throws UnauthorizedException
|
|
*/
|
|
public function rejectByRemoveSelf(Room $room, string $userId): void {
|
|
try {
|
|
$invitation = $this->invitationMapper->getInvitationForUserByLocalRoom($room, $userId);
|
|
} catch (DoesNotExistException $e) {
|
|
throw new \InvalidArgumentException('invitation');
|
|
}
|
|
|
|
$this->rejectInvitation($invitation, $userId);
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
* @throws UnauthorizedException
|
|
*/
|
|
protected function rejectInvitation(Invitation $invitation, string $userId): void {
|
|
$this->invitationMapper->delete($invitation);
|
|
$this->markNotificationProcessed($userId, $invitation->getId());
|
|
|
|
$this->backendNotifier->sendShareDeclined($invitation->getRemoteServerUrl(), $invitation->getRemoteAttendeeId(), $invitation->getAccessToken());
|
|
}
|
|
|
|
/**
|
|
* @param IUser $user
|
|
* @return Invitation[]
|
|
*/
|
|
public function getRemoteRoomShares(IUser $user): array {
|
|
return $this->invitationMapper->getInvitationsForUser($user);
|
|
}
|
|
|
|
public function getNumberOfPendingInvitationsForUser(IUser $user): int {
|
|
return $this->invitationMapper->countInvitationsForUser($user, Invitation::STATE_PENDING);
|
|
}
|
|
|
|
public function getNumberOfInvitations(Room $room): int {
|
|
return $this->invitationMapper->countInvitationsForLocalRoom($room);
|
|
}
|
|
}
|