From 616b3e01b6dbe6d1e29e0cecaea679f7f0eb965b Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 12 Mar 2024 16:47:49 +0100 Subject: [PATCH 1/2] fix(federation): Don't allow declining after accepting Signed-off-by: Joas Schilling --- lib/Controller/FederationController.php | 5 +-- lib/Federation/FederationManager.php | 5 +++ lib/Middleware/InjectionMiddleware.php | 2 +- openapi-federation.json | 35 +++++++++++++++++++ openapi-full.json | 35 +++++++++++++++++++ src/types/openapi/openapi-federation.ts | 13 +++++++ src/types/openapi/openapi-full.ts | 13 +++++++ .../features/federation/invite.feature | 2 ++ 8 files changed, 107 insertions(+), 3 deletions(-) diff --git a/lib/Controller/FederationController.php b/lib/Controller/FederationController.php index 2a8072672a4..a9b35260412 100644 --- a/lib/Controller/FederationController.php +++ b/lib/Controller/FederationController.php @@ -129,9 +129,10 @@ public function acceptShare(int $id): DataResponse { * * @param int $id ID of the share * @psalm-param non-negative-int $id - * @return DataResponse, array{}>|DataResponse + * @return DataResponse, array{}>|DataResponse * * 200: Invite declined successfully + * 400: Invite was already accepted, use the "Remove the current user from a room" endpoint instead * 404: Invite can not be found */ #[NoAdminRequired] @@ -146,7 +147,7 @@ public function rejectShare(int $id): DataResponse { } catch (UnauthorizedException $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); } catch (\InvalidArgumentException $e) { - return new DataResponse(['error' => $e->getMessage()], Http::STATUS_NOT_FOUND); + return new DataResponse(['error' => $e->getMessage()], $e->getMessage() === 'invitation' ? Http::STATUS_NOT_FOUND : Http::STATUS_BAD_REQUEST); } return new DataResponse(); } diff --git a/lib/Federation/FederationManager.php b/lib/Federation/FederationManager.php index d81d1e34476..6dfaf27ffcb 100644 --- a/lib/Federation/FederationManager.php +++ b/lib/Federation/FederationManager.php @@ -177,6 +177,11 @@ public function rejectRemoteRoomShare(IUser $user, int $shareId): void { } catch (DoesNotExistException $e) { throw new \InvalidArgumentException('invitation'); } + + if ($invitation->getState() !== Invitation::STATE_PENDING) { + throw new \InvalidArgumentException('state'); + } + if ($invitation->getUserId() !== $user->getUID()) { throw new UnauthorizedException('user'); } diff --git a/lib/Middleware/InjectionMiddleware.php b/lib/Middleware/InjectionMiddleware.php index 81279fcd359..31b961d24e7 100644 --- a/lib/Middleware/InjectionMiddleware.php +++ b/lib/Middleware/InjectionMiddleware.php @@ -259,13 +259,13 @@ protected function getRoomByInvite(AEnvironmentAwareController $controller): voi 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->setRoom($room); $controller->setInvitation($invitation); } catch (DoesNotExistException $e) { throw new ParticipantNotFoundException('No invite available', $e->getCode(), $e); diff --git a/openapi-federation.json b/openapi-federation.json index e71a6f6da54..7336aaa20f1 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -1221,6 +1221,41 @@ } } } + }, + "400": { + "description": "Invite was already accepted, use the \"Remove the current user from a room\" endpoint instead", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + } + } + } } } } diff --git a/openapi-full.json b/openapi-full.json index 50e739aad5f..cef8274f87c 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -16631,6 +16631,41 @@ } } } + }, + "400": { + "description": "Invite was already accepted, use the \"Remove the current user from a room\" endpoint instead", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + } + } + } } } } diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index a4893fc9401..912d7ee5cdf 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -425,6 +425,19 @@ export type operations = { }; }; }; + /** @description Invite was already accepted, use the "Remove the current user from a room" endpoint instead */ + 400: { + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error?: string; + }; + }; + }; + }; + }; /** @description Invite can not be found */ 404: { content: { diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 98121ac6349..02fcb75e689 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -5665,6 +5665,19 @@ export type operations = { }; }; }; + /** @description Invite was already accepted, use the "Remove the current user from a room" endpoint instead */ + 400: { + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error?: string; + }; + }; + }; + }; + }; /** @description Invite can not be found */ 404: { content: { diff --git a/tests/integration/features/federation/invite.feature b/tests/integration/features/federation/invite.feature index 0f4996b38da..49d0c9d510c 100644 --- a/tests/integration/features/federation/invite.feature +++ b/tests/integration/features/federation/invite.feature @@ -61,6 +61,8 @@ Feature: federation/invite | room | room | 3 | LOCAL | room | And user "participant2" accepts invite to room "room" of server "LOCAL" with 400 (v1) | error | state | + And user "participant2" declines invite to room "room" of server "LOCAL" with 400 (v1) + | error | state | And user "participant2" has the following invitations (v1) | remoteServerUrl | remoteToken | state | inviterCloudId | inviterDisplayName | | LOCAL | room | 1 | participant1@http://localhost:8080 | participant1-displayname | From 79e059936d708c36ffd601b334e92a68a3dcb1da Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 12 Mar 2024 16:06:07 +0100 Subject: [PATCH 2/2] test(federation): Add test for avatar federation Signed-off-by: Joas Schilling --- .../features/bootstrap/FeatureContext.php | 1 + .../features/federation/invite.feature | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index b0ce34b4f75..5efd6bf8a19 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -510,6 +510,7 @@ public function userHasInvites(string $user, string $apiVersion, TableNode $form foreach ($invites as $data) { self::$remoteToInviteId[$this->translateRemoteServer($data['remoteServerUrl']) . '::' . self::$tokenToIdentifier[$data['remoteToken']]] = $data['id']; self::$inviteIdToRemote[$data['id']] = $this->translateRemoteServer($data['remoteServerUrl']) . '::' . self::$tokenToIdentifier[$data['remoteToken']]; + self::$identifierToToken['LOCAL::' . $data['roomName']] = $data['localToken']; } } diff --git a/tests/integration/features/federation/invite.feature b/tests/integration/features/federation/invite.feature index 49d0c9d510c..9136077e5ad 100644 --- a/tests/integration/features/federation/invite.feature +++ b/tests/integration/features/federation/invite.feature @@ -229,3 +229,26 @@ Feature: federation/invite Then user "participant2" is participant of the following rooms (v4) | id | name | type | | room | Federated room | 2 | + + Scenario: Allow accessing conversation and room avatars for invited users + Given the following "spreed" app config is set + | federation_enabled | yes | + Given user "participant1" creates room "room" (v4) + | roomType | 2 | + | roomName | room | + And user "participant1" adds federated_user "participant2" to room "room" with 200 (v4) + And user "participant2" has the following invitations (v1) + | remoteServerUrl | remoteToken | state | inviterCloudId | inviterDisplayName | + | LOCAL | room | 0 | participant1@http://localhost:8080 | participant1-displayname | + When as user "participant2" + Then the room "LOCAL::room" has an avatar with 200 + And user "participant2" accepts invite to room "room" of server "LOCAL" with 200 (v1) + | id | name | type | remoteServer | remoteToken | + | room | room | 2 | LOCAL | room | + When as user "participant2" + Then the room "LOCAL::room" has an avatar with 200 + And user "participant2" removes themselves from room "LOCAL::room" with 200 (v4) + And user "participant2" has the following invitations (v1) + | remoteServerUrl | remoteToken | state | inviterCloudId | inviterDisplayName | + When as user "participant2" + Then the room "LOCAL::room" has an avatar with 404