🔒️(backend) enhance participant ID serialization in lobby per audit

Improve participant ID handling in lobby serialization following security
auditor recommendations to prevent potential data exposure.
This commit is contained in:
lebaudantoine
2025-06-24 12:24:02 +02:00
committed by aleb_the_flash
parent 64eadadaef
commit 1cd8fd2fc6
2 changed files with 46 additions and 27 deletions

View File

@@ -1,5 +1,7 @@
"""Client serializers for the Meet core app."""
import uuid
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
@@ -219,6 +221,14 @@ class ParticipantEntrySerializer(serializers.Serializer):
participant_id = serializers.CharField(required=True)
allow_entry = serializers.BooleanField(required=True)
def validate_participant_id(self, value):
"""Validate that the participant_id is a valid UUID hex string."""
try:
uuid.UUID(hex=value, version=4)
except (ValueError, TypeError) as e:
raise serializers.ValidationError("Invalid UUID hex format") from e
return value
def create(self, validated_data):
"""Not implemented as this is a validation-only serializer."""
raise NotImplementedError("ParticipantEntrySerializer is validation-only")

View File

@@ -132,18 +132,18 @@ def test_request_entry_with_existing_participants(settings):
# Add two participants already waiting in the lobby
cache.set(
f"mocked-cache-prefix_{room.id}_participant1",
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
{
"id": "participant1",
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
"username": "user1",
"status": "waiting",
"color": "#123456",
},
)
cache.set(
f"mocked-cache-prefix_{room.id}_participant2",
f"mocked-cache-prefix_{room.id}_f4ca3ab8a6c04ad88097b8da33f60f10",
{
"id": "participant2",
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
"username": "user2",
"status": "accepted",
"color": "#654321",
@@ -257,7 +257,9 @@ def test_request_entry_authenticated_user_public_room(settings):
with (
mock.patch.object(LobbyService, "notify_participants", return_value=None),
mock.patch.object(
LobbyService, "_get_or_create_participant_id", return_value="123"
LobbyService,
"_get_or_create_participant_id",
return_value="2f7f162fe7d1421b90e702bfbfbf8def",
),
mock.patch.object(
utils, "generate_livekit_config", return_value={"token": "test-token"}
@@ -274,11 +276,11 @@ def test_request_entry_authenticated_user_public_room(settings):
# Verify the lobby cookie was set
cookie = response.cookies.get("mocked-cookie")
assert cookie is not None
assert cookie.value == "123"
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
# Verify response content matches expected structure and values
assert response.json() == {
"id": "123",
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
"username": "test_user",
"status": "accepted",
"color": "mocked-color",
@@ -300,9 +302,9 @@ def test_request_entry_waiting_participant_public_room(settings):
# Add a waiting participant to the room's lobby cache
cache.set(
f"mocked-cache-prefix_{room.id}_participant1",
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
{
"id": "participant1",
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
"username": "user1",
"status": "waiting",
"color": "#123456",
@@ -310,7 +312,7 @@ def test_request_entry_waiting_participant_public_room(settings):
)
# Simulate a browser with existing participant cookie
client.cookies.load({"mocked-cookie": "participant1"})
client.cookies.load({"mocked-cookie": "2f7f162fe7d1421b90e702bfbfbf8def"})
with (
mock.patch.object(LobbyService, "notify_participants", return_value=None),
@@ -328,11 +330,11 @@ def test_request_entry_waiting_participant_public_room(settings):
# Verify the lobby cookie was set
cookie = response.cookies.get("mocked-cookie")
assert cookie is not None
assert cookie.value == "participant1"
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
# Verify response content matches expected structure and values
assert response.json() == {
"id": "participant1",
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
"username": "user1",
"status": "accepted",
"color": "#123456",
@@ -379,7 +381,7 @@ def test_allow_participant_to_enter_anonymous():
response = client.post(
f"/api/v1.0/rooms/{room.id}/enter/",
{"participant_id": "test-id", "allow_entry": True},
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
)
assert response.status_code == 401
@@ -394,7 +396,7 @@ def test_allow_participant_to_enter_non_owner():
response = client.post(
f"/api/v1.0/rooms/{room.id}/enter/",
{"participant_id": "test-id", "allow_entry": True},
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
)
assert response.status_code == 403
@@ -412,7 +414,7 @@ def test_allow_participant_to_enter_public_room():
response = client.post(
f"/api/v1.0/rooms/{room.id}/enter/",
{"participant_id": "test-id", "allow_entry": True},
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
)
assert response.status_code == 404
@@ -435,9 +437,9 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix"
cache.set(
f"mocked-cache-prefix_{room.id!s}_participant1",
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def",
{
"id": "test-id",
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
"status": "waiting",
"username": "foo",
"color": "123",
@@ -446,13 +448,18 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
response = client.post(
f"/api/v1.0/rooms/{room.id}/enter/",
{"participant_id": "participant1", "allow_entry": allow_entry},
{
"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def",
"allow_entry": allow_entry,
},
)
assert response.status_code == 200
assert response.json() == {"message": "Participant was updated."}
participant_data = cache.get(f"mocked-cache-prefix_{room.id!s}_participant1")
participant_data = cache.get(
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def"
)
assert participant_data.get("status") == updated_status
@@ -468,12 +475,14 @@ def test_allow_participant_to_enter_participant_not_found(settings):
settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix"
participant_data = cache.get(f"mocked-cache-prefix_{room.id!s}_test-id")
participant_data = cache.get(
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def"
)
assert participant_data is None
response = client.post(
f"/api/v1.0/rooms/{room.id}/enter/",
{"participant_id": "test-id", "allow_entry": True},
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
)
assert response.status_code == 404
@@ -563,18 +572,18 @@ def test_list_waiting_participants_success(settings):
# Add participants in the lobby
cache.set(
f"mocked-cache-prefix_{room.id}_participant1",
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
{
"id": "participant1",
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
"username": "user1",
"status": "waiting",
"color": "#123456",
},
)
cache.set(
f"mocked-cache-prefix_{room.id}_participant2",
f"mocked-cache-prefix_{room.id}_f4ca3ab8a6c04ad88097b8da33f60f10",
{
"id": "participant2",
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
"username": "user2",
"status": "waiting",
"color": "#654321",
@@ -588,13 +597,13 @@ def test_list_waiting_participants_success(settings):
participants = response.json().get("participants")
assert sorted(participants, key=lambda p: p["id"]) == [
{
"id": "participant1",
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
"username": "user1",
"status": "waiting",
"color": "#123456",
},
{
"id": "participant2",
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
"username": "user2",
"status": "waiting",
"color": "#654321",