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
8 changes: 7 additions & 1 deletion lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,7 @@ public function addParticipantToRoom(string $newParticipant, string $source = 'u
}
}

/** @var IUser $addedBy */
$addedBy = $this->userManager->get($this->userId);

// list of participants to attempt adding,
Expand Down Expand Up @@ -1153,7 +1154,12 @@ public function addParticipantToRoom(string $newParticipant, string $source = 'u
$this->logger->error($e->getMessage(), [
'exception' => $e,
]);
return new DataResponse([], Http::STATUS_BAD_REQUEST);
return new DataResponse(['error' => 'cloudId'], Http::STATUS_BAD_REQUEST);
}
try {
$this->federationManager->isAllowedToInvite($addedBy, $newUser);
} catch (\InvalidArgumentException $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}

$participantsToAdd[] = [
Expand Down
52 changes: 10 additions & 42 deletions lib/Federation/BackendNotifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,23 @@
namespace OCA\Talk\Federation;

use OCA\FederatedFileSharing\AddressHandler;
use OCA\Federation\TrustedServers;
use OCA\Talk\Config;
use OCA\Talk\Exceptions\RoomHasNoModeratorException;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\RetryNotification;
use OCA\Talk\Model\RetryNotificationMapper;
use OCA\Talk\Room;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\DB\Exception;
use OCP\Federation\ICloudFederationFactory;
use OCP\Federation\ICloudFederationNotification;
use OCP\Federation\ICloudFederationProviderManager;
use OCP\Federation\ICloudIdManager;
use OCP\HintException;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\OCM\Exceptions\OCMProviderException;
use OCP\Server;
use Psr\Log\LoggerInterface;
use SensitiveParameter;

Expand All @@ -59,11 +55,10 @@ public function __construct(
private ICloudFederationProviderManager $federationProviderManager,
private IUserManager $userManager,
private IURLGenerator $url,
private IAppManager $appManager,
private Config $talkConfig,
private IAppConfig $appConfig,
private RetryNotificationMapper $retryNotificationMapper,
private ITimeFactory $timeFactory,
private ICloudIdManager $cloudIdManager,
private RestrictionValidator $restrictionValidator,
) {
}

Expand All @@ -84,52 +79,25 @@ public function sendRemoteShare(
Room $room,
Attendee $roomOwnerAttendee,
): bool {
[$user, $remote] = $this->addressHandler->splitUserRemote($shareWith);
$invitedCloudId = $this->cloudIdManager->resolveCloudId($shareWith);

$roomName = $room->getName();
$roomType = $room->getType();
$roomToken = $room->getToken();

if (!($user && $remote)) {
$this->logger->info("Could not share conversation $roomToken as the recipient is invalid: $shareWith");
return false;
}

if (!$this->appConfig->getAppValueBool('federation_outgoing_enabled', true)) {
$this->logger->info("Could not share conversation $roomToken as outgoing federation is disabled");
return false;
}

if (!$this->talkConfig->isFederationEnabledForUserId($sharedBy)) {
$this->logger->info('Talk federation not allowed for user ' . $sharedBy->getUID());
try {
$this->restrictionValidator->isAllowedToInvite($sharedBy, $invitedCloudId);
} catch (\InvalidArgumentException) {
return false;
}

if ($this->appConfig->getAppValueBool('federation_only_trusted_servers')) {
if (!$this->appManager->isEnabledForUser('federation')) {
$this->logger->error('Federation is limited to trusted servers but the "federation" app is disabled');
return false;
}

$trustedServers = Server::get(TrustedServers::class);
$serverUrl = $this->addressHandler->removeProtocolFromUrl($remote);
if (!$trustedServers->isTrustedServer($serverUrl)) {
$this->logger->warning(
'Tried to send Talk federation invite to untrusted server {serverUrl}',
['serverUrl' => $serverUrl]
);
return false;
}
}

/** @var IUser $roomOwner */
$roomOwner = $this->userManager->get($roomOwnerAttendee->getActorId());

$invitedCloudId = $user . '@' . $remote;
$remote = $this->prepareRemoteUrl($remote);
$remote = $this->prepareRemoteUrl($invitedCloudId->getRemote());

$share = $this->cloudFederationFactory->getCloudFederationShare(
$user . '@' . $remote,
$invitedCloudId->getUser() . '@' . $remote,
$roomToken,
'',
$providerId,
Expand All @@ -144,7 +112,7 @@ public function sendRemoteShare(

// Put room name info in the share
$protocol = $share->getProtocol();
$protocol['invitedCloudId'] = $invitedCloudId;
$protocol['invitedCloudId'] = $invitedCloudId->getId();
$protocol['roomName'] = $roomName;
$protocol['roomType'] = $roomType;
$protocol['name'] = FederationManager::TALK_PROTOCOL_NAME;
Expand Down
14 changes: 14 additions & 0 deletions lib/Federation/FederationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Federation\ICloudId;
use OCP\IUser;
use OCP\Notification\IManager;
use SensitiveParameter;
Expand Down Expand Up @@ -67,9 +68,22 @@ public function __construct(
private InvitationMapper $invitationMapper,
private BackendNotifier $backendNotifier,
private IManager $notificationManager,
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,
Expand Down
89 changes: 89 additions & 0 deletions lib/Federation/RestrictionValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2024 Joas Schilling <[email protected]>
*
* @author Joas Schilling <[email protected]>
*
* @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\FederatedFileSharing\AddressHandler;
use OCA\Federation\TrustedServers;
use OCA\Talk\Config;
use OCP\App\IAppManager;
use OCP\AppFramework\Services\IAppConfig;
use OCP\Federation\ICloudId;
use OCP\IUser;
use OCP\Server;
use Psr\Log\LoggerInterface;

class RestrictionValidator {
public function __construct(
private AddressHandler $addressHandler,
private IAppManager $appManager,
private Config $talkConfig,
private IAppConfig $appConfig,
private LoggerInterface $logger,
) {
}

/**
* Check if $sharedBy is allowed to invite $shareWith
*
* @throws \InvalidArgumentException
*/
public function isAllowedToInvite(
IUser $user,
ICloudId $cloudIdToInvite,
): void {
if (!($cloudIdToInvite->getUser() && $cloudIdToInvite->getRemote())) {
$this->logger->debug('Could not share conversation as the recipient is invalid: ' . $cloudIdToInvite->getId());
throw new \InvalidArgumentException('cloudId');
}

if (!$this->appConfig->getAppValueBool('federation_outgoing_enabled', true)) {
$this->logger->debug('Could not share conversation as outgoing federation is disabled');
throw new \InvalidArgumentException('outgoing');
}

if (!$this->talkConfig->isFederationEnabledForUserId($user)) {
$this->logger->debug('Talk federation not allowed for user ' . $user->getUID());
throw new \InvalidArgumentException('federation');
}

if ($this->appConfig->getAppValueBool('federation_only_trusted_servers')) {
if (!$this->appManager->isEnabledForUser('federation')) {
$this->logger->error('Federation is limited to trusted servers but the "federation" app is disabled');
throw new \InvalidArgumentException('trusted_servers');
}

$trustedServers = Server::get(TrustedServers::class);
$serverUrl = $this->addressHandler->removeProtocolFromUrl($cloudIdToInvite->getRemote());
if (!$trustedServers->isTrustedServer($serverUrl)) {
$this->logger->warning(
'Tried to send Talk federation invite to untrusted server {serverUrl}',
['serverUrl' => $serverUrl]
);
throw new \InvalidArgumentException('trusted_servers');
}
}
}
}
22 changes: 15 additions & 7 deletions tests/php/Federation/FederationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use OCA\Talk\Federation\CloudFederationProviderTalk;
use OCA\Talk\Federation\FederationManager;
use OCA\Talk\Federation\Proxy\TalkV1\UserConverter;
use OCA\Talk\Federation\RestrictionValidator;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\AttendeeMapper;
Expand All @@ -49,6 +50,7 @@
use OCP\Federation\ICloudFederationNotification;
use OCP\Federation\ICloudFederationProviderManager;
use OCP\Federation\ICloudFederationShare;
use OCP\Federation\ICloudId;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IResponse;
use OCP\ICacheFactory;
Expand Down Expand Up @@ -104,6 +106,7 @@ class FederationTest extends TestCase {
protected ICacheFactory|MockObject $cacheFactory;
protected RetryNotificationMapper|MockObject $retryNotificationMapper;
protected ITimeFactory|MockObject $timeFactory;
protected RestrictionValidator|MockObject $restrictionValidator;

public function setUp(): void {
parent::setUp();
Expand All @@ -123,6 +126,7 @@ public function setUp(): void {
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->retryNotificationMapper = $this->createMock(RetryNotificationMapper::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->restrictionValidator = $this->createMock(RestrictionValidator::class);

$this->backendNotifier = new BackendNotifier(
$this->cloudFederationFactory,
Expand All @@ -131,11 +135,10 @@ public function setUp(): void {
$this->cloudFederationProviderManager,
$this->userManager,
$this->url,
$this->appManager,
$this->config,
$this->appConfig,
$this->retryNotificationMapper,
$this->timeFactory,
$this->cloudIdManager,
$this->restrictionValidator,
);

$this->federationManager = $this->createMock(FederationManager::class);
Expand Down Expand Up @@ -170,7 +173,6 @@ public function testSendRemoteShareWithOwner() {
$cloudShare = $this->createMock(ICloudFederationShare::class);

$providerId = '3';
$roomId = 5;
$token = 'abcdefghijklmno';
$shareWith = 'test@https://remote.test.local';
$name = 'abcdefgh';
Expand Down Expand Up @@ -246,10 +248,16 @@ public function testSendRemoteShareWithOwner() {
->method('sendCloudShare')
->with($cloudShare);

$this->addressHandler->expects($this->once())
->method('splitUserRemote')
$cloudId = $this->createMock(ICloudId::class);
$cloudId->method('getRemote')
->willReturn('remote.test.local');
$cloudId->method('getUser')
->willReturn('test');

$this->cloudIdManager->expects($this->once())
->method('resolveCloudId')
->with($shareWith)
->willReturn(['test', 'remote.test.local']);
->willReturn($cloudId);

$this->appConfig->method('getAppValueBool')
->willReturnMap([
Expand Down