diff --git a/lib/Controller/SignalingController.php b/lib/Controller/SignalingController.php index 22c37351758..3a594ba8c05 100644 --- a/lib/Controller/SignalingController.php +++ b/lib/Controller/SignalingController.php @@ -384,6 +384,23 @@ public function sendMessages(string $token, string $messages): DataResponse { if (!$participant->hasModeratorPermissions(false)) { break; } + } elseif ($decodedMessage['type'] === 'offer' || $decodedMessage['type'] === 'answer') { + $room = $this->manager->getRoomForSession($this->userId, $message['sessionId']); + $participant = $this->participantService->getParticipantBySession($room, $message['sessionId']); + + if (!($participant->getPermissions() & Attendee::PERMISSIONS_PUBLISH_AUDIO) && $decodedMessage['roomType'] === 'video' + && $this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'audio')) { + break; + } + if (!($participant->getPermissions() & Attendee::PERMISSIONS_PUBLISH_VIDEO) && $decodedMessage['roomType'] === 'video' + && $this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'video')) { + break; + } + if (!($participant->getPermissions() & Attendee::PERMISSIONS_PUBLISH_SCREEN) && $decodedMessage['roomType'] === 'screen' + && ($this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'audio') + || $this->isTryingToPublishMedia($decodedMessage['payload']['sdp'], 'video'))) { + break; + } } $this->messages->addMessage($message['sessionId'], $decodedMessage['to'], json_encode($decodedMessage)); @@ -395,6 +412,84 @@ public function sendMessages(string $token, string $messages): DataResponse { return new DataResponse($response); } + /** + * Returns whether the SDP is trying to publish the given media based on the + * media direction. + * + * The SDP is trying to publish if the related media description contains a + * media direction of either "sendrecv" or "sendonly". If no media direction + * is provided in a media description the media direction in the session + * description is used instead. If that is not provided either then + * "sendrecv" is assumed. + * + * See https://www.rfc-editor.org/rfc/rfc8866.html#name-media-direction-attributes + * + * @param string $sdp the SDP to check + * @param string $media the media to check, either "audio" or "video" + * @return bool true if it is trying to publish, false otherwise + */ + private function isTryingToPublishMedia(string $sdp, string $media): bool { + $lines = preg_split('/\r\n|\n|\r/', $sdp); + + $sessionMediaDirectionIndex = -1; + $mediaDirectionIndex = -1; + $mediaDescriptionIndex = -1; + $matchingMediaDescriptionIndex = -1; + + for ($i = 0; $i < count($lines); $i++) { + if (strpos($lines[$i], 'a=sendrecv') === 0 + || strpos($lines[$i], 'a=sendonly') === 0 + || strpos($lines[$i], 'a=recvonly') === 0 + || strpos($lines[$i], 'a=inactive') === 0) { + $mediaDirectionIndex = $i; + + if ($mediaDescriptionIndex < 0) { + $sessionMediaDirectionIndex = $mediaDirectionIndex; + } + + if ($matchingMediaDescriptionIndex >= 0 + && $matchingMediaDescriptionIndex >= $mediaDescriptionIndex + && $mediaDirectionIndex > $matchingMediaDescriptionIndex + && (strpos($lines[$mediaDirectionIndex], 'a=sendrecv') === 0 + || strpos($lines[$mediaDirectionIndex], 'a=sendonly') === 0)) { + return true; + } + } elseif (strpos($lines[$i], 'm=') === 0) { + // No media direction in previous matching media description, + // fallback to media direction in the session description or, if + // not set, default to "sendrecv". + if ($matchingMediaDescriptionIndex >= 0 + && $matchingMediaDescriptionIndex >= $mediaDescriptionIndex + && $mediaDirectionIndex < $matchingMediaDescriptionIndex + && ($sessionMediaDirectionIndex < 0 + || strpos($lines[$sessionMediaDirectionIndex], 'a=sendrecv') === 0 + || strpos($lines[$sessionMediaDirectionIndex], 'a=sendonly') === 0)) { + return true; + } + + $mediaDescriptionIndex = $i; + + if (strpos($lines[$i], 'm=' . $media) === 0) { + $matchingMediaDescriptionIndex = $i; + } + } + } + + // No media direction in last matching media description, fallback to + // media direction in the session description or, if not set, default to + // "sendrecv". + if ($matchingMediaDescriptionIndex >= 0 + && $matchingMediaDescriptionIndex >= $mediaDescriptionIndex + && $mediaDirectionIndex < $matchingMediaDescriptionIndex + && ($sessionMediaDirectionIndex < 0 + || strpos($lines[$sessionMediaDirectionIndex], 'a=sendrecv') === 0 + || strpos($lines[$sessionMediaDirectionIndex], 'a=sendonly') === 0)) { + return true; + } + + return false; + } + /** * Get signaling messages * diff --git a/tests/php/Controller/SignalingControllerTest.php b/tests/php/Controller/SignalingControllerTest.php index 69a8d104825..65a7d5876ad 100644 --- a/tests/php/Controller/SignalingControllerTest.php +++ b/tests/php/Controller/SignalingControllerTest.php @@ -144,6 +144,174 @@ private function recreateSignalingController() { ); } + public static function dataIsTryingToPublishMedia(): array { + // For simplicity the SDP contains only the relevant fields and it is + // not a valid SDP + return [ + // Audio publisher/receiver + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=sendrecv\n", + true, false, + ], + // Audio publisher/receiver with data channel + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=sendrecv\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" . + "a=sendrecv\n", + true, false, + ], + // Video publisher + [ + "m=video 42108 RTP/AVP 0\n" . + "a=sendonly\n", + false, true, + ], + // Video publisher with data channel + [ + "m=video 42108 RTP/AVP 0\n" . + "a=sendonly\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" . + "a=sendrecv\n", + false, true, + ], + // Audio and video publisher/receiver + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=sendrecv\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=sendrecv\n", + true, true, + ], + // Audio and video publisher/receiver with data channel + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=sendrecv\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=sendrecv\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" . + "a=sendrecv\n", + true, true, + ], + // Audio and video receiver with data channel + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=recvonly\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=recvonly\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" . + "a=sendrecv\n", + false, false, + ], + // Audio receiver and video inactive + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=recvonly\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=inactive\n", + false, false, + ], + // Audio receiver with session publisher/receiver direction + [ + "a=sendrecv\n" . + "m=audio 42108 RTP/AVP 0\n" . + "a=recvonly\n", + false, false, + ], + // Video inactive with session publisher direction and data channel + [ + "a=sendonly\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=inactive\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" . + "a=sendrecv\n", + false, false, + ], + // Audio and video with session publisher/receiver direction + [ + "a=sendrecv\n" . + "m=audio 42108 RTP/AVP 0\n" . + "m=video 42108 RTP/AVP 0\n", + true, true, + ], + // Audio and video with session publisher direction and data channel + [ + "a=sendonly\n" . + "m=audio 42108 RTP/AVP 0\n" . + "m=video 42108 RTP/AVP 0\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" . + "a=sendrecv\n", + true, true, + ], + // Audio and video with session receiver direction and data channel + [ + "a=recvonly\n" . + "m=audio 42108 RTP/AVP 0\n" . + "m=video 42108 RTP/AVP 0\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n" . + "a=sendrecv\n", + false, false, + ], + // Audio and video with implicit publisher/receiver direction + [ + "m=audio 42108 RTP/AVP 0\n" . + "m=video 42108 RTP/AVP 0\n", + true, true, + ], + // Audio and video with implicit publisher/receiver direction and + // data channel + [ + "m=audio 42108 RTP/AVP 0\n" . + "m=video 42108 RTP/AVP 0\n" . + "m=application 8 UDP/DTLS/SCTP webrtc-datachannel\n", + true, true, + ], + // No audio and video description with session publisher direction + [ + "a=sendonly\n", + false, false, + ], + // Several audio and video with mixed directions + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=inactive\n" . + "m=audio 42108 RTP/AVP 0\n" . + "a=sendrecv\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=recvonly\n" . + "m=audio 42108 RTP/AVP 0\n" . + "a=recvonly\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=recvonly\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=inactive\n", + true, false, + ], + // Several mixed directions in a single media description (not a + // valid SDP, but just in case) + [ + "m=audio 42108 RTP/AVP 0\n" . + "a=inactive\n" . + "a=sendrecv\n" . + "a=recvonly\n" . + "m=video 42108 RTP/AVP 0\n" . + "a=sendrecv\n" . + "a=recvonly\n" . + "a=inactive\n", + true, true, + ], + ]; + } + + /** + * @dataProvider dataIsTryingToPublishMedia + */ + public function testIsTryingToPublishMedia(string $sdp, bool $expectedAudioResult, bool $expectedVideoResult) { + $this->assertSame($expectedAudioResult, self::invokePrivate($this->controller, 'isTryingToPublishMedia', [$sdp, 'audio'])); + $this->assertSame($expectedVideoResult, self::invokePrivate($this->controller, 'isTryingToPublishMedia', [$sdp, 'video'])); + } + private function validateBackendRandom($data, $random, $checksum) { if (empty($random) || strlen($random) < 32) { return false;