diff --git a/docs/call.md b/docs/call.md index 99e9bbca081..6d8fd7fd058 100644 --- a/docs/call.md +++ b/docs/call.md @@ -53,6 +53,8 @@ + `200 OK` + `400 Bad Request` When recording consent is required but was not given + `403 Forbidden` When the conversation is read-only + + `403 Forbidden` When the user is not allowed to start a call and no call is currently active + + `403 Forbidden` When the conversation is a federated conversation and the host server did not allow joining the call + `404 Not Found` When the conversation could not be found for the participant + `404 Not Found` When the user did not join the conversation before + `412 Precondition Failed` When the lobby is active and the user is not a moderator diff --git a/lib/Controller/CallController.php b/lib/Controller/CallController.php index 7feed9a3da2..149e114c56b 100644 --- a/lib/Controller/CallController.php +++ b/lib/Controller/CallController.php @@ -218,10 +218,11 @@ protected function escapeFormulae(string $value): string { * or the capability is {@see RecordingService::CONSENT_REQUIRED_OPTIONAL} * and the conversation `recordingConsent` value is {@see RecordingService::CONSENT_REQUIRED_YES} ) * @param list $silentFor Send no call notification for previous participants - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * * 200: Call joined successfully * 400: No recording consent was given + * 403: Host server of the federated conversation does not allow joining the call * 404: Call not found */ #[FederationSupported] diff --git a/lib/Controller/SignalingController.php b/lib/Controller/SignalingController.php index b6a9bf73e25..249574ddca6 100644 --- a/lib/Controller/SignalingController.php +++ b/lib/Controller/SignalingController.php @@ -906,8 +906,18 @@ private function backendRoom(array $roomRequest): DataResponse { if ($participant->getSession() instanceof Session) { if ($inCall !== null) { $lastJoinedCall = $this->timeFactory->getDateTime(); - $this->participantService->changeInCall($room, $participant, $inCall, lastJoinedCall: $lastJoinedCall->getTimestamp()); - $this->roomService->setActiveSince($room, $participant, $lastJoinedCall, callFlag: $inCall, silent: false); + try { + $this->participantService->changeInCall($room, $participant, $inCall, lastJoinedCall: $lastJoinedCall->getTimestamp()); + $this->roomService->setActiveSince($room, $participant, $lastJoinedCall, callFlag: $inCall, silent: false); + } catch (ForbiddenException) { + return new DataResponse([ + 'type' => 'error', + 'error' => [ + 'code' => 'start_call_not_allowed', + 'message' => 'The participant is not allowed to start a call.', + ], + ]); + } } $this->sessionService->updateLastPing($participant->getSession(), $this->timeFactory->getTime()); } diff --git a/lib/Federation/Proxy/TalkV1/Controller/CallController.php b/lib/Federation/Proxy/TalkV1/Controller/CallController.php index a2fee70ce42..68e02c1c1c5 100644 --- a/lib/Federation/Proxy/TalkV1/Controller/CallController.php +++ b/lib/Federation/Proxy/TalkV1/Controller/CallController.php @@ -73,11 +73,12 @@ public function getPeersForCall(Room $room, Participant $participant): DataRespo * @psalm-param int-mask-of $flags * @param bool $silent Join the call silently * @param bool $recordingConsent Agreement to be recorded - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * @throws CannotReachRemoteException * * 200: Federated user is now in the call * 400: Conditions to join not met + * 403: Host server does not allow the user to join the call * 404: Room not found */ public function joinFederatedCall(Room $room, Participant $participant, int $flags, bool $silent, bool $recordingConsent): DataResponse { @@ -96,7 +97,7 @@ public function joinFederatedCall(Room $room, Participant $participant, int $fla ); $statusCode = $proxy->getStatusCode(); - if (!in_array($statusCode, [Http::STATUS_OK, Http::STATUS_BAD_REQUEST, Http::STATUS_NOT_FOUND], true)) { + if (!in_array($statusCode, [Http::STATUS_OK, Http::STATUS_BAD_REQUEST, Http::STATUS_FORBIDDEN, Http::STATUS_NOT_FOUND], true)) { $this->proxy->logUnexpectedStatusCode(__METHOD__, $proxy->getStatusCode()); throw new CannotReachRemoteException(); } diff --git a/openapi-full.json b/openapi-full.json index 0674f76bea0..04589a3f4f3 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -5423,6 +5423,36 @@ } } }, + "403": { + "description": "Host server of the federated conversation does not allow joining the call", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, "404": { "description": "Call not found", "content": { diff --git a/openapi.json b/openapi.json index e2a43294ae4..daa9218d117 100644 --- a/openapi.json +++ b/openapi.json @@ -5328,6 +5328,36 @@ } } }, + "403": { + "description": "Host server of the federated conversation does not allow joining the call", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, "404": { "description": "Call not found", "content": { diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 4c2de81173a..593711fb885 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -4395,6 +4395,20 @@ export interface operations { }; }; }; + /** @description Host server of the federated conversation does not allow joining the call */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; /** @description Call not found */ 404: { headers: { diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 1c391534cf7..84e41c80916 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -3829,6 +3829,20 @@ export interface operations { }; }; }; + /** @description Host server of the federated conversation does not allow joining the call */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; /** @description Call not found */ 404: { headers: { diff --git a/tests/integration/features/federation/call.feature b/tests/integration/features/federation/call.feature index ab0949204c5..9cce04ee030 100644 --- a/tests/integration/features/federation/call.feature +++ b/tests/integration/features/federation/call.feature @@ -62,6 +62,45 @@ Feature: federation/call | users | participant2 | 7 | And user "participant2" sees 2 peers in call "LOCAL::room" with 200 (v4) + @skip31 + Scenario: Federated user can not start a call when the host only allows moderators to start calls + Given the following "spreed" app config is set + | start_calls | 2 | + And user "participant1" creates room "room" (v4) + | roomType | 2 | + | roomName | room | + And user "participant1" adds federated_user "participant2" to room "room" with 200 (v4) + And using server "REMOTE" + And user "participant2" has the following invitations (v1) + | remoteServerUrl | remoteToken | state | inviterCloudId | inviterDisplayName | + | LOCAL | room | 0 | participant1@LOCAL | participant1-displayname | + And user "participant2" accepts invite to room "room" of server "LOCAL" with 200 (v1) + | id | name | type | remoteServer | remoteToken | + | LOCAL::room | room | 2 | LOCAL | room | + And user "participant2" joins room "LOCAL::room" with 200 (v4) + # The federated user is not a moderator on the host, so they are not allowed to start the call there + When user "participant2" joins call "LOCAL::room" with 403 (v4) + | flags | 7 | + Then user "participant2" is participant of room "LOCAL::room" (v4) + | callFlag | + | 0 | + # A moderator of the host starts the call + And using server "LOCAL" + And user "participant1" joins room "room" with 200 (v4) + And user "participant1" joins call "room" with 200 (v4) + | flags | 3 | + # Now the federated user is allowed to join the already running call + And using server "REMOTE" + And user "participant2" joins call "LOCAL::room" with 200 (v4) + | flags | 7 | + And user "participant2" is participant of room "LOCAL::room" (v4) + | callFlag | + | 7 | + And user "participant2" sees the following attendees in room "LOCAL::room" with 200 (v4) + | actorType | actorId | inCall | + | federated_users | participant1@{$LOCAL_URL} | 3 | + | users | participant2 | 7 | + Scenario: update call flags Given user "participant1" creates room "room" (v4) | roomType | 2 |