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.
247 lines
7.6 KiB
Python
247 lines
7.6 KiB
Python
"""Client serializers for the Meet core app."""
|
|
|
|
# pylint: disable=W0223
|
|
|
|
import uuid
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from rest_framework import serializers
|
|
from rest_framework.exceptions import PermissionDenied
|
|
from timezone_field.rest_framework import TimeZoneSerializerField
|
|
|
|
from core import models, utils
|
|
|
|
|
|
class UserSerializer(serializers.ModelSerializer):
|
|
"""Serialize users."""
|
|
|
|
timezone = TimeZoneSerializerField()
|
|
|
|
class Meta:
|
|
model = models.User
|
|
fields = ["id", "email", "full_name", "short_name", "timezone", "language"]
|
|
read_only_fields = ["id", "email", "full_name", "short_name"]
|
|
|
|
|
|
class ResourceAccessSerializerMixin:
|
|
"""
|
|
A serializer mixin to share controlling that the logged-in user submitting a room access object
|
|
is administrator on the targeted room.
|
|
"""
|
|
|
|
# pylint: disable=too-many-boolean-expressions
|
|
def validate(self, data):
|
|
"""
|
|
Check access rights specific to writing (create/update)
|
|
"""
|
|
request = self.context.get("request", None)
|
|
user = getattr(request, "user", None)
|
|
if (
|
|
# Update
|
|
self.instance
|
|
and (
|
|
data.get("role") == models.RoleChoices.OWNER
|
|
and not self.instance.resource.is_owner(user)
|
|
or self.instance.role == models.RoleChoices.OWNER
|
|
and self.instance.user != user
|
|
)
|
|
) or (
|
|
# Create
|
|
not self.instance
|
|
and data.get("role") == models.RoleChoices.OWNER
|
|
and not data["resource"].is_owner(user)
|
|
):
|
|
raise PermissionDenied(
|
|
"Only owners of a room can assign other users as owners."
|
|
)
|
|
return data
|
|
|
|
def validate_resource(self, resource):
|
|
"""The logged-in user must be administrator of the resource."""
|
|
request = self.context.get("request", None)
|
|
user = getattr(request, "user", None)
|
|
|
|
if not (
|
|
user and user.is_authenticated and resource.is_administrator_or_owner(user)
|
|
):
|
|
raise PermissionDenied(
|
|
_("You must be administrator or owner of a room to add accesses to it.")
|
|
)
|
|
|
|
return resource
|
|
|
|
|
|
class ResourceAccessSerializer(
|
|
ResourceAccessSerializerMixin, serializers.ModelSerializer
|
|
):
|
|
"""Serialize Room to User accesses for the API."""
|
|
|
|
class Meta:
|
|
model = models.ResourceAccess
|
|
fields = ["id", "user", "resource", "role"]
|
|
read_only_fields = ["id"]
|
|
|
|
def update(self, instance, validated_data):
|
|
"""Make "user" and "resource" fields readonly but only on update."""
|
|
validated_data.pop("resource", None)
|
|
validated_data.pop("user", None)
|
|
return super().update(instance, validated_data)
|
|
|
|
|
|
class NestedResourceAccessSerializer(ResourceAccessSerializer):
|
|
"""Serialize Room accesses for the API with full nested user."""
|
|
|
|
user = UserSerializer(read_only=True)
|
|
|
|
|
|
class ListRoomSerializer(serializers.ModelSerializer):
|
|
"""Serialize Room model for a list API endpoint."""
|
|
|
|
class Meta:
|
|
model = models.Room
|
|
fields = ["id", "name", "slug", "access_level"]
|
|
read_only_fields = ["id", "slug"]
|
|
|
|
|
|
class RoomSerializer(serializers.ModelSerializer):
|
|
"""Serialize Room model for the API."""
|
|
|
|
class Meta:
|
|
model = models.Room
|
|
fields = ["id", "name", "slug", "configuration", "access_level", "pin_code"]
|
|
read_only_fields = ["id", "slug", "pin_code"]
|
|
|
|
def to_representation(self, instance):
|
|
"""
|
|
Add users only for administrator users.
|
|
Add LiveKit credentials for public instance or related users/groups
|
|
"""
|
|
output = super().to_representation(instance)
|
|
request = self.context.get("request")
|
|
|
|
if not request:
|
|
return output
|
|
|
|
role = instance.get_role(request.user)
|
|
is_admin_or_owner = models.RoleChoices.check_administrator_role(
|
|
role
|
|
) or models.RoleChoices.check_owner_role(role)
|
|
|
|
if is_admin_or_owner:
|
|
access_serializer = NestedResourceAccessSerializer(
|
|
instance.accesses.select_related("resource", "user").all(),
|
|
context=self.context,
|
|
many=True,
|
|
)
|
|
output["accesses"] = access_serializer.data
|
|
|
|
configuration = output["configuration"]
|
|
|
|
if not is_admin_or_owner:
|
|
del output["configuration"]
|
|
|
|
should_access_room = (
|
|
(
|
|
instance.access_level == models.RoomAccessLevel.TRUSTED
|
|
and request.user.is_authenticated
|
|
)
|
|
or role is not None
|
|
or instance.is_public
|
|
)
|
|
|
|
if should_access_room:
|
|
room_id = f"{instance.id!s}"
|
|
username = request.query_params.get("username", None)
|
|
output["livekit"] = utils.generate_livekit_config(
|
|
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
|
|
|
|
return output
|
|
|
|
|
|
class RecordingSerializer(serializers.ModelSerializer):
|
|
"""Serialize Recording for the API."""
|
|
|
|
room = ListRoomSerializer(read_only=True)
|
|
|
|
class Meta:
|
|
model = models.Recording
|
|
fields = [
|
|
"id",
|
|
"room",
|
|
"created_at",
|
|
"updated_at",
|
|
"status",
|
|
"mode",
|
|
"key",
|
|
"is_expired",
|
|
"expired_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
|
|
class BaseValidationOnlySerializer(serializers.Serializer):
|
|
"""Base serializer for validation-only operations."""
|
|
|
|
def create(self, validated_data):
|
|
"""Not implemented as this is a validation-only serializer."""
|
|
raise NotImplementedError(f"{self.__class__.__name__} is validation-only")
|
|
|
|
def update(self, instance, validated_data):
|
|
"""Not implemented as this is a validation-only serializer."""
|
|
raise NotImplementedError(f"{self.__class__.__name__} is validation-only")
|
|
|
|
|
|
class StartRecordingSerializer(BaseValidationOnlySerializer):
|
|
"""Validate start recording requests."""
|
|
|
|
mode = serializers.ChoiceField(
|
|
choices=models.RecordingModeChoices.choices,
|
|
required=True,
|
|
error_messages={
|
|
"required": "Recording mode is required.",
|
|
"invalid_choice": "Invalid recording mode. Choose between "
|
|
"screen_recording or transcript.",
|
|
},
|
|
)
|
|
|
|
|
|
class RequestEntrySerializer(BaseValidationOnlySerializer):
|
|
"""Validate request entry data."""
|
|
|
|
username = serializers.CharField(required=True)
|
|
|
|
|
|
class ParticipantEntrySerializer(BaseValidationOnlySerializer):
|
|
"""Validate participant entry decision data."""
|
|
|
|
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
|
|
|
|
|
|
class CreationCallbackSerializer(BaseValidationOnlySerializer):
|
|
"""Validate room creation callback data."""
|
|
|
|
callback_id = serializers.CharField(required=True)
|
|
|
|
|
|
class RoomInviteSerializer(serializers.Serializer):
|
|
"""Validate room invite creation data."""
|
|
|
|
emails = serializers.ListField(child=serializers.EmailField(), allow_empty=False)
|