🔒️(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:
committed by
aleb_the_flash
parent
64eadadaef
commit
1cd8fd2fc6
@@ -1,5 +1,7 @@
|
|||||||
"""Client serializers for the Meet core app."""
|
"""Client serializers for the Meet core app."""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@@ -219,6 +221,14 @@ class ParticipantEntrySerializer(serializers.Serializer):
|
|||||||
participant_id = serializers.CharField(required=True)
|
participant_id = serializers.CharField(required=True)
|
||||||
allow_entry = serializers.BooleanField(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):
|
def create(self, validated_data):
|
||||||
"""Not implemented as this is a validation-only serializer."""
|
"""Not implemented as this is a validation-only serializer."""
|
||||||
raise NotImplementedError("ParticipantEntrySerializer is validation-only")
|
raise NotImplementedError("ParticipantEntrySerializer is validation-only")
|
||||||
|
|||||||
@@ -132,18 +132,18 @@ def test_request_entry_with_existing_participants(settings):
|
|||||||
|
|
||||||
# Add two participants already waiting in the lobby
|
# Add two participants already waiting in the lobby
|
||||||
cache.set(
|
cache.set(
|
||||||
f"mocked-cache-prefix_{room.id}_participant1",
|
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
{
|
{
|
||||||
"id": "participant1",
|
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
cache.set(
|
cache.set(
|
||||||
f"mocked-cache-prefix_{room.id}_participant2",
|
f"mocked-cache-prefix_{room.id}_f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||||
{
|
{
|
||||||
"id": "participant2",
|
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
@@ -257,7 +257,9 @@ def test_request_entry_authenticated_user_public_room(settings):
|
|||||||
with (
|
with (
|
||||||
mock.patch.object(LobbyService, "notify_participants", return_value=None),
|
mock.patch.object(LobbyService, "notify_participants", return_value=None),
|
||||||
mock.patch.object(
|
mock.patch.object(
|
||||||
LobbyService, "_get_or_create_participant_id", return_value="123"
|
LobbyService,
|
||||||
|
"_get_or_create_participant_id",
|
||||||
|
return_value="2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
),
|
),
|
||||||
mock.patch.object(
|
mock.patch.object(
|
||||||
utils, "generate_livekit_config", return_value={"token": "test-token"}
|
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
|
# Verify the lobby cookie was set
|
||||||
cookie = response.cookies.get("mocked-cookie")
|
cookie = response.cookies.get("mocked-cookie")
|
||||||
assert cookie is not None
|
assert cookie is not None
|
||||||
assert cookie.value == "123"
|
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
|
||||||
|
|
||||||
# Verify response content matches expected structure and values
|
# Verify response content matches expected structure and values
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"id": "123",
|
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
"username": "test_user",
|
"username": "test_user",
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"color": "mocked-color",
|
"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
|
# Add a waiting participant to the room's lobby cache
|
||||||
cache.set(
|
cache.set(
|
||||||
f"mocked-cache-prefix_{room.id}_participant1",
|
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
{
|
{
|
||||||
"id": "participant1",
|
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
@@ -310,7 +312,7 @@ def test_request_entry_waiting_participant_public_room(settings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Simulate a browser with existing participant cookie
|
# Simulate a browser with existing participant cookie
|
||||||
client.cookies.load({"mocked-cookie": "participant1"})
|
client.cookies.load({"mocked-cookie": "2f7f162fe7d1421b90e702bfbfbf8def"})
|
||||||
|
|
||||||
with (
|
with (
|
||||||
mock.patch.object(LobbyService, "notify_participants", return_value=None),
|
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
|
# Verify the lobby cookie was set
|
||||||
cookie = response.cookies.get("mocked-cookie")
|
cookie = response.cookies.get("mocked-cookie")
|
||||||
assert cookie is not None
|
assert cookie is not None
|
||||||
assert cookie.value == "participant1"
|
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
|
||||||
|
|
||||||
# Verify response content matches expected structure and values
|
# Verify response content matches expected structure and values
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"id": "participant1",
|
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
@@ -379,7 +381,7 @@ def test_allow_participant_to_enter_anonymous():
|
|||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
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
|
assert response.status_code == 401
|
||||||
@@ -394,7 +396,7 @@ def test_allow_participant_to_enter_non_owner():
|
|||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
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
|
assert response.status_code == 403
|
||||||
@@ -412,7 +414,7 @@ def test_allow_participant_to_enter_public_room():
|
|||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
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
|
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"
|
settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix"
|
||||||
|
|
||||||
cache.set(
|
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",
|
"status": "waiting",
|
||||||
"username": "foo",
|
"username": "foo",
|
||||||
"color": "123",
|
"color": "123",
|
||||||
@@ -446,13 +448,18 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
|
|||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
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.status_code == 200
|
||||||
assert response.json() == {"message": "Participant was updated."}
|
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
|
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"
|
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
|
assert participant_data is None
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
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
|
assert response.status_code == 404
|
||||||
@@ -563,18 +572,18 @@ def test_list_waiting_participants_success(settings):
|
|||||||
|
|
||||||
# Add participants in the lobby
|
# Add participants in the lobby
|
||||||
cache.set(
|
cache.set(
|
||||||
f"mocked-cache-prefix_{room.id}_participant1",
|
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
{
|
{
|
||||||
"id": "participant1",
|
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
cache.set(
|
cache.set(
|
||||||
f"mocked-cache-prefix_{room.id}_participant2",
|
f"mocked-cache-prefix_{room.id}_f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||||
{
|
{
|
||||||
"id": "participant2",
|
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
@@ -588,13 +597,13 @@ def test_list_waiting_participants_success(settings):
|
|||||||
participants = response.json().get("participants")
|
participants = response.json().get("participants")
|
||||||
assert sorted(participants, key=lambda p: p["id"]) == [
|
assert sorted(participants, key=lambda p: p["id"]) == [
|
||||||
{
|
{
|
||||||
"id": "participant1",
|
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "participant2",
|
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
|
|||||||
Reference in New Issue
Block a user