fix(federation): Allow accessing avatars with pending invites

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2024-03-12 15:06:12 +01:00
parent 04d51b3514
commit 5907dc435c
No known key found for this signature in database
GPG key ID: 74434EFE0D2E2205
6 changed files with 115 additions and 4 deletions

View file

@ -28,6 +28,7 @@ declare(strict_types=1);
namespace OCA\Talk\Controller;
use OC\AppFramework\Http\Dispatcher;
use OCA\Talk\Model\Invitation;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCP\AppFramework\OCSController;
@ -36,6 +37,7 @@ abstract class AEnvironmentAwareController extends OCSController {
protected int $apiVersion = 1;
protected ?Room $room = null;
protected ?Participant $participant = null;
protected ?Invitation $invitation = null;
public function setAPIVersion(int $apiVersion): void {
$this->apiVersion = $apiVersion;
@ -61,6 +63,14 @@ abstract class AEnvironmentAwareController extends OCSController {
return $this->participant;
}
public function setInvitation(Invitation $invitation): void {
$this->invitation = $invitation;
}
public function getInvitation(): ?Invitation {
return $this->invitation;
}
/**
* Following the logic of {@see Dispatcher::executeController}
* @return string Either 'json' or 'xml'

View file

@ -29,6 +29,7 @@ namespace OCA\Talk\Controller;
use InvalidArgumentException;
use OCA\Talk\Exceptions\CannotReachRemoteException;
use OCA\Talk\Middleware\Attribute\AllowWithoutParticipantWhenPendingInvitation;
use OCA\Talk\Middleware\Attribute\FederationSupported;
use OCA\Talk\Middleware\Attribute\RequireLoggedInParticipant;
use OCA\Talk\Middleware\Attribute\RequireModeratorParticipant;
@ -143,13 +144,14 @@ class AvatarController extends AEnvironmentAwareController {
#[FederationSupported]
#[PublicPage]
#[NoCSRFRequired]
#[AllowWithoutParticipantWhenPendingInvitation]
#[RequireParticipantOrLoggedInAndListedConversation]
public function getAvatar(bool $darkTheme = false): FileDisplayResponse {
if ($this->room->getRemoteServer() !== '') {
/** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\AvatarController $proxy */
$proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\AvatarController::class);
try {
return $proxy->getAvatar($this->room, $this->participant, $darkTheme);
return $proxy->getAvatar($this->room, $this->participant, $this->invitation, $darkTheme);
} catch (CannotReachRemoteException) {
// Falling back to a local "globe" avatar for indicating the federation
}
@ -172,6 +174,7 @@ class AvatarController extends AEnvironmentAwareController {
#[FederationSupported]
#[PublicPage]
#[NoCSRFRequired]
#[AllowWithoutParticipantWhenPendingInvitation]
#[RequireParticipantOrLoggedInAndListedConversation]
public function getAvatarDark(): FileDisplayResponse {
return $this->getAvatar(true);
@ -193,6 +196,7 @@ class AvatarController extends AEnvironmentAwareController {
#[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)]
#[PublicPage]
#[NoCSRFRequired]
#[AllowWithoutParticipantWhenPendingInvitation]
#[RequireLoggedInParticipant]
public function getUserProxyAvatar(int $size, string $cloudId, bool $darkTheme = false): FileDisplayResponse {
try {
@ -252,6 +256,7 @@ class AvatarController extends AEnvironmentAwareController {
#[OpenAPI(scope: OpenAPI::SCOPE_FEDERATION)]
#[PublicPage]
#[NoCSRFRequired]
#[AllowWithoutParticipantWhenPendingInvitation]
#[RequireLoggedInParticipant]
public function getUserProxyAvatarDark(int $size, string $cloudId): FileDisplayResponse {
return $this->getUserProxyAvatar($size, $cloudId, true);

View file

@ -28,6 +28,7 @@ namespace OCA\Talk\Federation\Proxy\TalkV1\Controller;
use OCA\Talk\Exceptions\CannotReachRemoteException;
use OCA\Talk\Federation\Proxy\TalkV1\ProxyRequest;
use OCA\Talk\Model\Invitation;
use OCA\Talk\Participant;
use OCA\Talk\ResponseDefinitions;
use OCA\Talk\Room;
@ -53,10 +54,14 @@ class AvatarController {
*
* 200: Room avatar returned
*/
public function getAvatar(Room $room, Participant $participant, bool $darkTheme): FileDisplayResponse {
public function getAvatar(Room $room, ?Participant $participant, ?Invitation $invitation, bool $darkTheme): FileDisplayResponse {
if ($participant === null && $invitation === null) {
throw new CannotReachRemoteException('Must receive either participant or invitation');
}
$proxy = $this->proxy->get(
$participant->getAttendee()->getInvitedCloudId(),
$participant->getAttendee()->getAccessToken(),
$participant ? $participant->getAttendee()->getInvitedCloudId() : $invitation->getLocalCloudId(),
$participant ? $participant->getAttendee()->getAccessToken() : $invitation->getAccessToken(),
$room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v1/room/' . $room->getRemoteToken() . '/avatar' . ($darkTheme ? '/dark' : ''),
);

View file

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2024 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @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\Middleware\Attribute;
use Attribute;
use OCA\Talk\Middleware\InjectionMiddleware;
/**
* @see InjectionMiddleware::getRoomByInvite()
*/
#[Attribute(Attribute::TARGET_METHOD)]
class AllowWithoutParticipantWhenPendingInvitation extends RequireRoom {
}

View file

@ -30,6 +30,7 @@ use OCA\Talk\Exceptions\PermissionsException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Federation\Authenticator;
use OCA\Talk\Manager;
use OCA\Talk\Middleware\Attribute\AllowWithoutParticipantWhenPendingInvitation;
use OCA\Talk\Middleware\Attribute\FederationSupported;
use OCA\Talk\Middleware\Attribute\RequireAuthenticatedParticipant;
use OCA\Talk\Middleware\Attribute\RequireLoggedInModeratorParticipant;
@ -46,12 +47,14 @@ use OCA\Talk\Middleware\Exceptions\LobbyException;
use OCA\Talk\Middleware\Exceptions\NotAModeratorException;
use OCA\Talk\Middleware\Exceptions\ReadOnlyException;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\InvitationMapper;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\TalkSession;
use OCA\Talk\Webinary;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\RedirectResponse;
@ -74,6 +77,7 @@ class InjectionMiddleware extends Middleware {
protected ICloudIdManager $cloudIdManager,
protected IThrottler $throttler,
protected IURLGenerator $url,
protected InvitationMapper $invitationMapper,
protected Authenticator $federationAuthenticator,
protected ?string $userId,
) {
@ -98,6 +102,15 @@ class InjectionMiddleware extends Middleware {
$apiVersion = $this->request->getParam('apiVersion');
$controller->setAPIVersion((int) substr($apiVersion, 1));
if (!empty($reflectionMethod->getAttributes(AllowWithoutParticipantWhenPendingInvitation::class))) {
try {
$this->getRoomByInvite($controller);
return;
} catch (RoomNotFoundException|ParticipantNotFoundException) {
// Falling back to bellow checks
}
}
if (!empty($reflectionMethod->getAttributes(RequireAuthenticatedParticipant::class))) {
$this->getLoggedInOrGuest($controller, false, requireFederationWhenNotLoggedIn: true);
}
@ -232,6 +245,34 @@ class InjectionMiddleware extends Middleware {
}
}
/**
* @param AEnvironmentAwareController $controller
* @throws RoomNotFoundException
* @throws ParticipantNotFoundException
*/
protected function getRoomByInvite(AEnvironmentAwareController $controller): void {
if ($this->userId === null) {
throw new ParticipantNotFoundException('No user available');
}
$room = $controller->getRoom();
if (!$room instanceof Room) {
$token = $this->request->getParam('token');
$room = $this->manager->getRoomByToken($token);
$controller->setRoom($room);
}
$participant = $controller->getParticipant();
if (!$participant instanceof Participant) {
try {
$invitation = $this->invitationMapper->getInvitationsForUserByLocalRoom($room, $this->userId);
$controller->setInvitation($invitation);
} catch (DoesNotExistException $e) {
throw new ParticipantNotFoundException('No invite available', $e->getCode(), $e);
}
}
}
/**
* @param AEnvironmentAwareController $controller
* @throws FederationUnsupportedFeatureException

View file

@ -95,6 +95,20 @@ class InvitationMapper extends QBMapper {
return $this->findEntities($qb);
}
/**
* @throws DoesNotExistException
*/
public function getInvitationsForUserByLocalRoom(Room $room, string $userId): Invitation {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('user_id', $query->createNamedParameter($userId)))
->andWhere($query->expr()->eq('local_room_id', $query->createNamedParameter($room->getId())));
return $this->findEntity($query);
}
public function countInvitationsForLocalRoom(Room $room): int {
$qb = $this->db->getQueryBuilder();