зеркало из https://github.com/nextcloud/spreed.git
349 строки
12 KiB
PHP
349 строки
12 KiB
PHP
<?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\Config;
|
|
use OCA\Talk\Events\AttendeesAddedEvent;
|
|
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\EventDispatcher\IEventDispatcher;
|
|
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\ISession;
|
|
use OCP\IURLGenerator;
|
|
use OCP\IUser;
|
|
use OCP\IUserManager;
|
|
use OCP\Notification\IManager as INotificationManager;
|
|
use OCP\Share\Exceptions\ShareNotFound;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class CloudFederationProviderTalk implements ICloudFederationProvider {
|
|
private IUserManager $userManager;
|
|
|
|
private AddressHandler $addressHandler;
|
|
|
|
private FederationManager $federationManager;
|
|
|
|
private Config $config;
|
|
|
|
private INotificationManager $notificationManager;
|
|
|
|
private IURLGenerator $urlGenerator;
|
|
|
|
private ParticipantService $participantService;
|
|
|
|
private AttendeeMapper $attendeeMapper;
|
|
|
|
private Manager $manager;
|
|
private ISession $session;
|
|
private IEventDispatcher $dispatcher;
|
|
private LoggerInterface $logger;
|
|
|
|
public function __construct(
|
|
IUserManager $userManager,
|
|
AddressHandler $addressHandler,
|
|
FederationManager $federationManager,
|
|
Config $config,
|
|
INotificationManager $notificationManager,
|
|
IURLGenerator $urlGenerator,
|
|
ParticipantService $participantService,
|
|
AttendeeMapper $attendeeMapper,
|
|
Manager $manager,
|
|
ISession $session,
|
|
IEventDispatcher $dispatcher,
|
|
LoggerInterface $logger
|
|
) {
|
|
$this->userManager = $userManager;
|
|
$this->addressHandler = $addressHandler;
|
|
$this->federationManager = $federationManager;
|
|
$this->config = $config;
|
|
$this->notificationManager = $notificationManager;
|
|
$this->urlGenerator = $urlGenerator;
|
|
$this->participantService = $participantService;
|
|
$this->attendeeMapper = $attendeeMapper;
|
|
$this->manager = $manager;
|
|
$this->session = $session;
|
|
$this->dispatcher = $dispatcher;
|
|
$this->logger = $logger;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getShareType(): string {
|
|
return 'talk-room';
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws HintException
|
|
* @throws DBException
|
|
*/
|
|
public function shareReceived(ICloudFederationShare $share): string {
|
|
if (!$this->config->isFederationEnabled()) {
|
|
$this->logger->debug('Received a federation invite but federation is disabled');
|
|
throw new ProviderCouldNotAddShareException('Server does not support talk federation', '', Http::STATUS_SERVICE_UNAVAILABLE);
|
|
}
|
|
if (!in_array($share->getShareType(), $this->getSupportedShareTypes(), true)) {
|
|
$this->logger->debug('Received a federation invite for invalid share type');
|
|
throw new ProviderCouldNotAddShareException('Support for sharing with non-users not implemented yet', '', Http::STATUS_NOT_IMPLEMENTED);
|
|
// TODO: Implement group shares
|
|
}
|
|
|
|
$roomType = $share->getProtocol()['roomType'];
|
|
if (!is_numeric($roomType) || !in_array((int) $roomType, $this->validSharedRoomTypes(), true)) {
|
|
$this->logger->debug('Received a federation invite for invalid room type');
|
|
throw new ProviderCouldNotAddShareException('roomType is not a valid number', '', Http::STATUS_BAD_REQUEST);
|
|
}
|
|
|
|
$shareSecret = $share->getShareSecret();
|
|
$shareWith = $share->getShareWith();
|
|
$remoteId = $share->getProviderId();
|
|
$roomToken = $share->getResourceName();
|
|
$roomName = $share->getProtocol()['roomName'];
|
|
$roomType = (int) $roomType;
|
|
$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) {
|
|
$this->logger->debug('Received a federation invite for user that could not be found');
|
|
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;
|
|
}
|
|
|
|
$this->logger->debug('Received a federation invite with missing request data');
|
|
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 $this->shareUnshared((int) $providerId, $notification);
|
|
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']);
|
|
|
|
$this->session->set('talk-overwrite-actor-type', $attendee->getActorType());
|
|
$this->session->set('talk-overwrite-actor-id', $attendee->getActorId());
|
|
|
|
$room = $this->manager->getRoomById($attendee->getRoomId());
|
|
$event = new AttendeesAddedEvent($room, [$attendee]);
|
|
$this->dispatcher->dispatchTyped($event);
|
|
|
|
$this->session->remove('talk-overwrite-actor-type');
|
|
$this->session->remove('talk-overwrite-actor-id');
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @throws ActionNotSupportedException
|
|
* @throws ShareNotFound
|
|
* @throws AuthenticationFailedException
|
|
*/
|
|
private function shareDeclined(int $id, array $notification): array {
|
|
$attendee = $this->getAttendeeAndValidate($id, $notification['sharedSecret']);
|
|
|
|
$this->session->set('talk-overwrite-actor-type', $attendee->getActorType());
|
|
$this->session->set('talk-overwrite-actor-id', $attendee->getActorId());
|
|
|
|
$room = $this->manager->getRoomById($attendee->getRoomId());
|
|
$participant = new Participant($room, $attendee, null);
|
|
$this->participantService->removeAttendee($room, $participant, Room::PARTICIPANT_LEFT);
|
|
|
|
$this->session->remove('talk-overwrite-actor-type');
|
|
$this->session->remove('talk-overwrite-actor-id');
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
*/
|
|
private function getAttendeeAndValidate(int $id, string $sharedSecret): Attendee {
|
|
if (!$this->config->isFederationEnabled()) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @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): void {
|
|
$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', ['apiVersion' => 'v1', 'id' => $shareId]), 'DELETE');
|
|
$notification->addAction($declineAction);
|
|
|
|
$acceptAction = $notification->createAction();
|
|
$acceptAction->setLabel('accept')
|
|
->setLink($this->urlGenerator->linkToOCSRouteAbsolute('spreed.Federation.acceptShare', ['apiVersion' => 'v1', 'id' => $shareId]), 'POST');
|
|
$notification->addAction($acceptAction);
|
|
|
|
$this->notificationManager->notify($notification);
|
|
}
|
|
|
|
private function validSharedRoomTypes(): array {
|
|
return [
|
|
Room::TYPE_ONE_TO_ONE,
|
|
Room::TYPE_GROUP,
|
|
Room::TYPE_PUBLIC,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getSupportedShareTypes(): array {
|
|
return ['user'];
|
|
}
|
|
}
|