Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
use OCA\Talk\Federation\Proxy\TalkV1\Notifier\BeforeRoomDeletedListener as TalkV1BeforeRoomDeletedListener;
use OCA\Talk\Federation\Proxy\TalkV1\Notifier\CancelRetryOCMListener as TalkV1CancelRetryOCMListener;
use OCA\Talk\Federation\Proxy\TalkV1\Notifier\MessageSentListener as TalkV1MessageSentListener;
use OCA\Talk\Federation\Proxy\TalkV1\Notifier\ParticipantModifiedListener as TalkV1ParticipantModifiedListener;
use OCA\Talk\Federation\Proxy\TalkV1\Notifier\RoomModifiedListener as TalkV1RoomModifiedListener;
use OCA\Talk\Files\Listener as FilesListener;
use OCA\Talk\Files\TemplateLoader as FilesTemplateLoader;
Expand Down Expand Up @@ -268,6 +269,7 @@ public function register(IRegistrationContext $context): void {

// Federation listeners
$context->registerEventListener(BeforeRoomDeletedEvent::class, TalkV1BeforeRoomDeletedListener::class);
$context->registerEventListener(ParticipantModifiedEvent::class, TalkV1ParticipantModifiedListener::class);
$context->registerEventListener(CallEndedEvent::class, TalkV1RoomModifiedListener::class);
$context->registerEventListener(CallEndedForEveryoneEvent::class, TalkV1RoomModifiedListener::class);
$context->registerEventListener(CallStartedEvent::class, TalkV1RoomModifiedListener::class);
Expand Down
36 changes: 36 additions & 0 deletions lib/Federation/BackendNotifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public function sendRemoteShare(
$roomName = $room->getName();
$roomType = $room->getType();
$roomToken = $room->getToken();
$roomDefaultPermissions = $room->getDefaultPermissions();

try {
$this->restrictionValidator->isAllowedToInvite($sharedBy, $invitedCloudId);
Expand Down Expand Up @@ -101,6 +102,7 @@ public function sendRemoteShare(
$protocol['invitedCloudId'] = $invitedCloudId->getId();
$protocol['roomName'] = $roomName;
$protocol['roomType'] = $roomType;
$protocol['roomDefaultPermissions'] = $roomDefaultPermissions;
$protocol['name'] = FederationManager::TALK_PROTOCOL_NAME;
$share->setProtocol($protocol);

Expand Down Expand Up @@ -251,6 +253,40 @@ public function sendRoomModifiedUpdate(
return $this->sendUpdateToRemote($remote, $notification);
}

/**
* Send information to remote participants that the participant meta info updated
* Sent from Host server to Remote participant server (only for the affected participant)
*/
public function sendParticipantModifiedUpdate(
string $remoteServer,
int $localAttendeeId,
#[SensitiveParameter]
string $accessToken,
string $localToken,
string $changedProperty,
string|int $newValue,
string|int|null $oldValue,
): ?bool {
$remote = $this->prepareRemoteUrl($remoteServer);

$notification = $this->cloudFederationFactory->getCloudFederationNotification();
$notification->setMessage(
FederationManager::NOTIFICATION_PARTICIPANT_MODIFIED,
FederationManager::TALK_ROOM_RESOURCE,
(string) $localAttendeeId,
[
'remoteServerUrl' => $this->getServerRemoteUrl(),
'sharedSecret' => $accessToken,
'remoteToken' => $localToken,
'changedProperty' => $changedProperty,
'newValue' => $newValue,
'oldValue' => $oldValue,
],
);

return $this->sendUpdateToRemote($remote, $notification);
}

/**
* Send information to remote participants that "active since" was updated
* Sent from Host server to Remote participant server
Expand Down
43 changes: 42 additions & 1 deletion lib/Federation/CloudFederationProviderTalk.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public function shareReceived(ICloudFederationShare $share): string {
$remoteId = $share->getProviderId();
$roomToken = $share->getResourceName();
$roomName = $share->getProtocol()['roomName'];
$roomDefaultPermissions = $share->getProtocol()['roomDefaultPermissions'] ?? Attendee::PERMISSIONS_DEFAULT;
if (isset($share->getProtocol()['invitedCloudId'])) {
$localCloudId = $share->getProtocol()['invitedCloudId'];
} else {
Expand Down Expand Up @@ -173,7 +174,7 @@ public function shareReceived(ICloudFederationShare $share): string {
throw new ProviderCouldNotAddShareException('User does not exist', '', Http::STATUS_BAD_REQUEST);
}

$invite = $this->federationManager->addRemoteRoom($shareWithUser, (int) $remoteId, $roomType, $roomName, $roomToken, $remote, $shareSecret, $sharedByFederatedId, $sharedByDisplayName, $localCloudId);
$invite = $this->federationManager->addRemoteRoom($shareWithUser, (int) $remoteId, $roomType, $roomName, $roomDefaultPermissions, $roomToken, $remote, $shareSecret, $sharedByFederatedId, $sharedByDisplayName, $localCloudId);

$this->notifyAboutNewShare($shareWithUser, (string) $invite->getId(), $sharedByFederatedId, $sharedByDisplayName, $roomName, $roomToken, $remote);
return (string) $invite->getId();
Expand All @@ -197,6 +198,8 @@ public function notificationReceived($notificationType, $providerId, array $noti
return $this->shareDeclined((int) $providerId, $notification);
case FederationManager::NOTIFICATION_SHARE_UNSHARED:
return $this->shareUnshared((int) $providerId, $notification);
case FederationManager::NOTIFICATION_PARTICIPANT_MODIFIED:
return $this->participantModified((int) $providerId, $notification);
case FederationManager::NOTIFICATION_ROOM_MODIFIED:
return $this->roomModified((int) $providerId, $notification);
case FederationManager::NOTIFICATION_MESSAGE_POSTED:
Expand Down Expand Up @@ -292,6 +295,42 @@ private function shareUnshared(int $remoteAttendeeId, array $notification): arra
return [];
}

/**
* @param int $remoteAttendeeId
* @param array{remoteServerUrl: string, sharedSecret: string, remoteToken: string, changedProperty: string, newValue: string|int, oldValue: string|int|null} $notification
* @return array
* @throws ActionNotSupportedException
* @throws AuthenticationFailedException
* @throws ShareNotFound
*/
private function participantModified(int $remoteAttendeeId, array $notification): array {
$invite = $this->getByRemoteAttendeeAndValidate($notification['remoteServerUrl'], $remoteAttendeeId, $notification['sharedSecret']);
try {
$room = $this->manager->getRoomById($invite->getLocalRoomId());
} catch (RoomNotFoundException) {
throw new ShareNotFound(FederationManager::OCM_RESOURCE_NOT_FOUND);
}

// Sanity check to make sure the room is a remote room
if (!$room->isFederatedConversation()) {
throw new ShareNotFound(FederationManager::OCM_RESOURCE_NOT_FOUND);
}

try {
$participant = $this->participantService->getParticipant($room, $invite->getUserId());
} catch (ParticipantNotFoundException $e) {
throw new ShareNotFound(FederationManager::OCM_RESOURCE_NOT_FOUND);
}

if ($notification['changedProperty'] === AParticipantModifiedEvent::PROPERTY_PERMISSIONS) {
$this->participantService->updatePermissions($room, $participant, Attendee::PERMISSIONS_MODIFY_SET, $notification['newValue']);
} else {
$this->logger->debug('Update of participant property "' . $notification['changedProperty'] . '" is not handled and should not be send via federation');
}

return [];
}

/**
* @param int $remoteAttendeeId
* @param array{remoteServerUrl: string, sharedSecret: string, remoteToken: string, changedProperty: string, newValue: string|int|bool|null, oldValue: string|int|bool|null, callFlag?: int, dateTime?: string, timerReached?: bool, details?: array<AParticipantModifiedEvent::DETAIL_*, bool>} $notification
Expand Down Expand Up @@ -324,6 +363,8 @@ private function roomModified(int $remoteAttendeeId, array $notification): array
$this->roomService->setAvatar($room, $notification['newValue']);
} elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_CALL_RECORDING) {
$this->roomService->setCallRecording($room, $notification['newValue']);
} elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS) {
$this->roomService->setDefaultPermissions($room, $notification['newValue']);
} elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_DESCRIPTION) {
$this->roomService->setDescription($room, $notification['newValue']);
} elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_IN_CALL) {
Expand Down
11 changes: 11 additions & 0 deletions lib/Federation/FederationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
Expand All @@ -42,13 +43,15 @@ class FederationManager {
public const NOTIFICATION_SHARE_ACCEPTED = 'SHARE_ACCEPTED';
public const NOTIFICATION_SHARE_DECLINED = 'SHARE_DECLINED';
public const NOTIFICATION_SHARE_UNSHARED = 'SHARE_UNSHARED';
public const NOTIFICATION_PARTICIPANT_MODIFIED = 'PARTICIPANT_MODIFIED';
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 RoomService $roomService,
private InvitationMapper $invitationMapper,
private BackendNotifier $backendNotifier,
private IManager $notificationManager,
Expand All @@ -74,6 +77,7 @@ public function addRemoteRoom(
int $remoteAttendeeId,
int $roomType,
string $roomName,
int $roomDefaultPermissions,
string $remoteToken,
string $remoteServerUrl,
#[SensitiveParameter]
Expand All @@ -90,6 +94,13 @@ public function addRemoteRoom(
$room = $this->manager->createRemoteRoom($roomType, $roomName, $remoteToken, $remoteServerUrl);
}

// Only update the room permissions if there are no participants in the
// remote room. Otherwise, the room permissions would be up to date
// already due to the notifications about room permission changes.
if (!$this->participantService->getNumberOfActors($room)) {
$this->roomService->setDefaultPermissions($room, $roomDefaultPermissions);
}

if ($couldHaveInviteWithOtherCasing) {
try {
$this->invitationMapper->getInvitationForUserByLocalRoom($room, $user->getUID(), true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\Federation\Proxy\TalkV1\Notifier;

use OCA\Talk\Events\AAttendeeRemovedEvent;
use OCA\Talk\Events\AParticipantModifiedEvent;
use OCA\Talk\Events\ParticipantModifiedEvent;
use OCA\Talk\Federation\BackendNotifier;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Service\ParticipantService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Federation\ICloudId;
use OCP\Federation\ICloudIdManager;

/**
* @template-implements IEventListener<Event>
*/
class ParticipantModifiedListener implements IEventListener {
public function __construct(
protected BackendNotifier $backendNotifier,
protected ParticipantService $participantService,
protected ICloudIdManager $cloudIdManager,
) {
}

public function handle(Event $event): void {
if (!$event instanceof ParticipantModifiedEvent) {
return;
}

$participant = $event->getParticipant();
if ($participant->getAttendee()->getActorType() !== Attendee::ACTOR_FEDERATED_USERS) {
return;
}

if (!in_array($event->getProperty(), [
AParticipantModifiedEvent::PROPERTY_PERMISSIONS,
], true)) {
return;
}

// For modifying participants we only notify the affected participant's server
$cloudId = $this->cloudIdManager->resolveCloudId($participant->getAttendee()->getActorId());
$success = $this->notifyParticipantModified($cloudId, $participant, $event);

if ($success === null) {
$this->participantService->removeAttendee($event->getRoom(), $participant, AAttendeeRemovedEvent::REASON_LEFT);
}
}

private function notifyParticipantModified(ICloudId $cloudId, Participant $participant, AParticipantModifiedEvent $event): ?bool {
return $this->backendNotifier->sendParticipantModifiedUpdate(
$cloudId->getRemote(),
$participant->getAttendee()->getId(),
$participant->getAttendee()->getAccessToken(),
$event->getRoom()->getToken(),
$event->getProperty(),
$event->getNewValue(),
$event->getOldValue(),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public function handle(Event $event): void {
ARoomModifiedEvent::PROPERTY_ACTIVE_SINCE,
ARoomModifiedEvent::PROPERTY_AVATAR,
ARoomModifiedEvent::PROPERTY_CALL_RECORDING,
ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS,
ARoomModifiedEvent::PROPERTY_DESCRIPTION,
ARoomModifiedEvent::PROPERTY_IN_CALL,
ARoomModifiedEvent::PROPERTY_LOBBY,
Expand Down
9 changes: 6 additions & 3 deletions tests/integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -562,9 +562,6 @@ private function assertRooms(array $rooms, TableNode $formData, bool $shouldOrde
if (isset($expectedRoom['permissions'])) {
$data['permissions'] = $this->mapPermissionsAPIOutput($room['permissions']);
}
if (isset($expectedRoom['permissions'])) {
$data['permissions'] = $this->mapPermissionsAPIOutput($room['permissions']);
}
if (isset($expectedRoom['attendeePermissions'])) {
$data['attendeePermissions'] = $this->mapPermissionsAPIOutput($room['attendeePermissions']);
}
Expand Down Expand Up @@ -2019,6 +2016,12 @@ public function userSetsPermissionsForInRoomTo(string $user, string $participant
} elseif (strpos($participant, 'guest') === 0) {
$sessionId = self::$userToSessionId[$participant];
$attendeeId = $this->getAttendeeId('guests', sha1($sessionId), $identifier, $statusCode === 200 ? $user : null);
} elseif (str_ends_with($participant, '@{$LOCAL_REMOTE_URL}') ||
str_ends_with($participant, '@{$REMOTE_URL}')) {
$participant = str_replace('{$LOCAL_REMOTE_URL}', rtrim($this->localRemoteServerUrl, '/'), $participant);
$participant = str_replace('{$REMOTE_URL}', rtrim($this->remoteServerUrl, '/'), $participant);

$attendeeId = $this->getAttendeeId('federated_users', $participant, $identifier, $statusCode === 200 ? $user : null);
} else {
$attendeeId = $this->getAttendeeId('users', $participant, $identifier, $statusCode === 200 ? $user : null);
}
Expand Down
Loading