💩(backend) pass room config and user role data to LiveKit token utility

Extend LiveKit token creation utility with additional room configuration
and user role parameters to properly adapt room_admin grants and
publish sources based on permission levels.

This creates technical debt in utility function design that should be
refactored into proper service architecture for token
generation operations in future iterations.
This commit is contained in:
lebaudantoine
2025-08-25 19:04:26 +02:00
committed by aleb_the_flash
parent fd7a78e80e
commit 0f76517957
5 changed files with 82 additions and 15 deletions

View File

@@ -136,6 +136,8 @@ class RoomSerializer(serializers.ModelSerializer):
) )
output["accesses"] = access_serializer.data output["accesses"] = access_serializer.data
configuration = output["configuration"]
if not is_admin_or_owner: if not is_admin_or_owner:
del output["configuration"] del output["configuration"]
@@ -152,7 +154,11 @@ class RoomSerializer(serializers.ModelSerializer):
room_id = f"{instance.id!s}" room_id = f"{instance.id!s}"
username = request.query_params.get("username", None) username = request.query_params.get("username", None)
output["livekit"] = utils.generate_livekit_config( output["livekit"] = utils.generate_livekit_config(
room_id=room_id, user=request.user, username=username room_id=room_id,
user=request.user,
username=username,
configuration=configuration,
is_admin_or_owner=is_admin_or_owner,
) )
output["is_administrable"] = is_admin_or_owner output["is_administrable"] = is_admin_or_owner

View File

@@ -143,6 +143,8 @@ class LobbyService:
participant_id = self._get_or_create_participant_id(request) participant_id = self._get_or_create_participant_id(request)
participant = self._get_participant(room.id, participant_id) participant = self._get_participant(room.id, participant_id)
room_id = str(room.id)
if self.can_bypass_lobby(room=room, user=request.user): if self.can_bypass_lobby(room=room, user=request.user):
if participant is None: if participant is None:
participant = LobbyParticipant( participant = LobbyParticipant(
@@ -155,10 +157,12 @@ class LobbyService:
participant.status = LobbyParticipantStatus.ACCEPTED participant.status = LobbyParticipantStatus.ACCEPTED
livekit_config = utils.generate_livekit_config( livekit_config = utils.generate_livekit_config(
room_id=str(room.id), room_id=room_id,
user=request.user, user=request.user,
username=username, username=username,
color=participant.color, color=participant.color,
configuration=room.configuration,
is_admin_or_owner=False,
) )
return participant, livekit_config return participant, livekit_config
@@ -173,10 +177,12 @@ class LobbyService:
elif participant.status == LobbyParticipantStatus.ACCEPTED: elif participant.status == LobbyParticipantStatus.ACCEPTED:
# wrongly named, contains access token to join a room # wrongly named, contains access token to join a room
livekit_config = utils.generate_livekit_config( livekit_config = utils.generate_livekit_config(
room_id=str(room.id), room_id=room_id,
user=request.user, user=request.user,
username=username, username=username,
color=participant.color, color=participant.color,
configuration=room.configuration,
is_admin_or_owner=False,
) )
return participant, livekit_config return participant, livekit_config

View File

@@ -235,7 +235,10 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
which they are not related, provided the room is public. which they are not related, provided the room is public.
They should not see related users. They should not see related users.
""" """
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC) room = RoomFactory(
access_level=RoomAccessLevel.PUBLIC,
configuration={"can_publish_sources": ["mock-source"]},
)
user = UserFactory() user = UserFactory()
client = APIClient() client = APIClient()
@@ -262,7 +265,12 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
} }
mock_token.assert_called_once_with( mock_token.assert_called_once_with(
room=expected_name, user=user, username=None, color=None room=expected_name,
user=user,
username=None,
color=None,
sources=["mock-source"],
is_admin_or_owner=False,
) )
@@ -307,7 +315,12 @@ def test_api_rooms_retrieve_authenticated_trusted(mock_token):
} }
mock_token.assert_called_once_with( mock_token.assert_called_once_with(
room=expected_name, user=user, username=None, color=None room=expected_name,
user=user,
username=None,
color=None,
sources=None,
is_admin_or_owner=False,
) )
@@ -353,7 +366,9 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries, setti
user = UserFactory() user = UserFactory()
other_user = UserFactory() other_user = UserFactory()
room = RoomFactory() room = RoomFactory(
configuration={"can_publish_sources": ["mock-source"]},
)
UserResourceAccessFactory(resource=room, user=user, role="member") UserResourceAccessFactory(resource=room, user=user, role="member")
UserResourceAccessFactory(resource=room, user=other_user, role="member") UserResourceAccessFactory(resource=room, user=other_user, role="member")
@@ -386,7 +401,12 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries, setti
} }
mock_token.assert_called_once_with( mock_token.assert_called_once_with(
room=expected_name, user=user, username=None, color=None room=expected_name,
user=user,
username=None,
color=None,
sources=["mock-source"],
is_admin_or_owner=False,
) )
@@ -473,5 +493,10 @@ def test_api_rooms_retrieve_administrators(
} }
mock_token.assert_called_once_with( mock_token.assert_called_once_with(
room=expected_name, user=user, username=None, color=None room=expected_name,
user=user,
username=None,
color=None,
sources=None,
is_admin_or_owner=True,
) )

View File

@@ -266,6 +266,8 @@ def test_request_entry_public_room(
user=request.user, user=request.user,
username=username, username=username,
color=participant.color, color=participant.color,
configuration=room.configuration,
is_admin_or_owner=False,
) )
lobby_service._get_participant.assert_called_once_with(room.id, participant_id) lobby_service._get_participant.assert_called_once_with(room.id, participant_id)
@@ -302,6 +304,8 @@ def test_request_entry_trusted_room(
user=request.user, user=request.user,
username=username, username=username,
color=participant.color, color=participant.color,
configuration=room.configuration,
is_admin_or_owner=False,
) )
lobby_service._get_participant.assert_called_once_with(room.id, participant_id) lobby_service._get_participant.assert_called_once_with(room.id, participant_id)
@@ -394,6 +398,8 @@ def test_request_entry_accepted_participant(
user=request.user, user=request.user,
username=username, username=username,
color="#123456", color="#123456",
configuration=room.configuration,
is_admin_or_owner=False,
) )
lobby_service._get_participant.assert_called_once_with(room.id, participant_id) lobby_service._get_participant.assert_called_once_with(room.id, participant_id)

View File

@@ -2,7 +2,8 @@
Utils functions used in the core app Utils functions used in the core app
""" """
# ruff: noqa:S311 # pylint: disable=R0913, R0917
# ruff: noqa:S311, PLR0913
import hashlib import hashlib
import json import json
@@ -54,6 +55,7 @@ def generate_token(
username: Optional[str] = None, username: Optional[str] = None,
color: Optional[str] = None, color: Optional[str] = None,
sources: Optional[List[str]] = None, sources: Optional[List[str]] = None,
is_admin_or_owner: bool = False,
) -> str: ) -> str:
"""Generate a LiveKit access token for a user in a specific room. """Generate a LiveKit access token for a user in a specific room.
@@ -66,20 +68,24 @@ def generate_token(
If none, a value will be generated If none, a value will be generated
sources: (Optional[List[str]]): List of media sources the user can publish sources: (Optional[List[str]]): List of media sources the user can publish
If none, defaults to LIVEKIT_DEFAULT_SOURCES. If none, defaults to LIVEKIT_DEFAULT_SOURCES.
is_admin_or_owner (bool): Whether user has admin privileges
Returns: Returns:
str: The LiveKit JWT access token. str: The LiveKit JWT access token.
""" """
if is_admin_or_owner:
sources = settings.LIVEKIT_DEFAULT_SOURCES
if sources is None: if sources is None:
sources = settings.LIVEKIT_DEFAULT_SOURCES sources = settings.LIVEKIT_DEFAULT_SOURCES
video_grants = VideoGrants( video_grants = VideoGrants(
room=room, room=room,
room_join=True, room_join=True,
room_admin=True, room_admin=is_admin_or_owner,
can_update_own_metadata=True, can_update_own_metadata=True,
can_publish=bool(len(sources)), can_publish=bool(sources),
can_publish_sources=sources, can_publish_sources=sources,
) )
@@ -101,14 +107,19 @@ def generate_token(
.with_grants(video_grants) .with_grants(video_grants)
.with_identity(identity) .with_identity(identity)
.with_name(username or default_username) .with_name(username or default_username)
.with_metadata(json.dumps({"color": color})) .with_metadata(json.dumps({"color": color, "room_admin": is_admin_or_owner}))
) )
return token.to_jwt() return token.to_jwt()
def generate_livekit_config( def generate_livekit_config(
room_id: str, user, username: str, color: Optional[str] = None room_id: str,
user,
username: str,
is_admin_or_owner: bool,
color: Optional[str] = None,
configuration: Optional[dict] = None,
) -> dict: ) -> dict:
"""Generate LiveKit configuration for room access. """Generate LiveKit configuration for room access.
@@ -116,15 +127,28 @@ def generate_livekit_config(
room_id: Room identifier room_id: Room identifier
user: User instance requesting access user: User instance requesting access
username: Display name in room username: Display name in room
is_admin_or_owner (bool): Whether the user has admin/owner privileges for this room.
color (Optional[str]): Optional color to associate with the participant.
configuration (Optional[dict]): Room configuration dict that can override default settings.
Returns: Returns:
dict: LiveKit configuration with URL, room and access token dict: LiveKit configuration with URL, room and access token
""" """
sources = None
if configuration is not None:
sources = configuration.get("can_publish_sources", None)
return { return {
"url": settings.LIVEKIT_CONFIGURATION["url"], "url": settings.LIVEKIT_CONFIGURATION["url"],
"room": room_id, "room": room_id,
"token": generate_token( "token": generate_token(
room=room_id, user=user, username=username, color=color room=room_id,
user=user,
username=username,
color=color,
sources=sources,
is_admin_or_owner=is_admin_or_owner,
), ),
} }