wip advanced

This commit is contained in:
Thomas Ramé
2026-04-02 18:52:11 +02:00
parent 316008016c
commit d7ce25b1b5
35 changed files with 865 additions and 166 deletions
+31 -4
View File
@@ -30,8 +30,8 @@ class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ["id", "email", "full_name", "short_name", "timezone", "language"]
read_only_fields = ["id", "email", "full_name", "short_name"]
fields = ["id", "sub", "email", "full_name", "short_name", "timezone", "language"]
read_only_fields = ["id", "sub", "email", "full_name", "short_name"]
class UserLightSerializer(serializers.ModelSerializer):
@@ -74,6 +74,23 @@ class ResourceAccessSerializerMixin:
raise PermissionDenied(
"Only owners of a room can assign other users as owners."
)
# In advanced encrypted rooms, new accesses require an encrypted_symmetric_key
# so the new member can decrypt the room's streams. Without it, they'd have
# access but no key — which is useless and confusing.
# Future: a sharing UI (like Docs) could provide the key via vault shareKeys.
if not self.instance and "resource" in data:
resource = data["resource"]
if (
hasattr(resource, 'encryption_mode')
and resource.encryption_mode == models.EncryptionMode.ADVANCED
and not data.get("encrypted_symmetric_key")
):
raise serializers.ValidationError(
"Adding members to advanced encrypted rooms requires "
"an encrypted_symmetric_key for the new user."
)
return data
def validate_resource(self, resource):
@@ -98,7 +115,7 @@ class ResourceAccessSerializer(
class Meta:
model = models.ResourceAccess
fields = ["id", "user", "resource", "role"]
fields = ["id", "user", "resource", "role", "encrypted_symmetric_key"]
read_only_fields = ["id"]
def update(self, instance, validated_data):
@@ -202,12 +219,22 @@ class RoomSerializer(serializers.ModelSerializer):
username=username,
configuration=configuration,
is_admin_or_owner=is_admin_or_owner,
encryption_mode=instance.encryption_mode,
)
else:
del output["pin_code"]
output["is_administrable"] = is_admin_or_owner
# Include the current user's encrypted symmetric key for advanced E2EE
if request.user.is_authenticated and instance.encryption_mode == models.EncryptionMode.ADVANCED:
try:
access = instance.accesses.get(user=request.user)
if access.encrypted_symmetric_key:
output["encrypted_symmetric_key"] = access.encrypted_symmetric_key
except models.ResourceAccess.DoesNotExist:
pass
return output
@@ -289,7 +316,7 @@ class StartRecordingSerializer(BaseValidationOnlySerializer):
class RequestEntrySerializer(BaseValidationOnlySerializer):
"""Validate request entry data."""
username = serializers.CharField(required=True)
username = serializers.CharField(required=True, allow_blank=True)
ephemeral_public_key = serializers.CharField(required=False, allow_blank=True, default='')
+15
View File
@@ -287,10 +287,12 @@ class RoomViewSet(
serializer.validated_data["access_level"] = models.RoomAccessLevel.RESTRICTED
room = serializer.save()
encrypted_symmetric_key = self.request.data.get("encrypted_symmetric_key", "")
models.ResourceAccess.objects.create(
resource=room,
user=self.request.user,
role=models.RoleChoices.OWNER,
encrypted_symmetric_key=encrypted_symmetric_key,
)
if callback_id := self.request.data.get("callback_id"):
@@ -319,6 +321,12 @@ class RoomViewSet(
options = serializer.validated_data.get("options")
room = self.get_object()
if room.encryption_enabled:
return drf_response.Response(
{"detail": "Recording is not available in encrypted rooms."},
status=drf_status.HTTP_403_FORBIDDEN,
)
# May raise exception if an active or initiated recording already exist for the room
recording = models.Recording.objects.create(
room=room,
@@ -403,6 +411,13 @@ class RoomViewSet(
room = self.get_object()
validated_data = serializer.validated_data
# Advanced encrypted rooms require authentication
if room.encryption_mode == models.EncryptionMode.ADVANCED and not request.user.is_authenticated:
return drf_response.Response(
{"detail": "This meeting requires authentication to join."},
status=drf_status.HTTP_403_FORBIDDEN,
)
# In encrypted rooms, authenticated users must use their real name
# from the OIDC profile — they cannot choose an arbitrary name.
if room.encryption_enabled and request.user.is_authenticated:
@@ -0,0 +1,23 @@
"""Add encrypted_symmetric_key to ResourceAccess for advanced E2EE mode."""
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0020_room_encryption_mode"),
]
operations = [
migrations.AddField(
model_name="resourceaccess",
name="encrypted_symmetric_key",
field=models.TextField(
blank=True,
default="",
help_text="Vault-wrapped symmetric encryption key for advanced E2EE mode. Each user's copy is encrypted for their own vault public key.",
verbose_name="Encrypted symmetric key",
),
),
]
+9
View File
@@ -332,6 +332,15 @@ class ResourceAccess(BaseModel):
role = models.CharField(
max_length=20, choices=RoleChoices.choices, default=RoleChoices.MEMBER
)
encrypted_symmetric_key = models.TextField(
blank=True,
default='',
verbose_name=_("Encrypted symmetric key"),
help_text=_(
"Vault-wrapped symmetric encryption key for advanced E2EE mode. "
"Each user's copy is encrypted for their own vault public key."
),
)
class Meta:
db_table = "meet_resource_access"
+3 -1
View File
@@ -127,7 +127,7 @@ class LobbyService:
key=settings.LOBBY_COOKIE_NAME,
value=participant_id,
httponly=True,
secure=True,
secure=not settings.DEBUG,
samesite="Lax",
)
@@ -193,6 +193,7 @@ class LobbyService:
configuration=room.configuration,
is_admin_or_owner=False,
participant_id=participant_id,
encryption_mode=room.encryption_mode,
)
return participant, livekit_config
@@ -235,6 +236,7 @@ class LobbyService:
configuration=room.configuration,
is_admin_or_owner=False,
participant_id=participant_id,
encryption_mode=room.encryption_mode,
)
return participant, livekit_config
+8 -1
View File
@@ -66,6 +66,7 @@ def generate_token(
sources: Optional[List[str]] = None,
is_admin_or_owner: bool = False,
participant_id: Optional[str] = None,
encryption_mode: str = 'none',
) -> str:
"""Generate a LiveKit access token for a user in a specific room.
@@ -92,11 +93,15 @@ def generate_token(
if sources is None:
sources = settings.LIVEKIT_DEFAULT_SOURCES
# In encrypted rooms, authenticated users cannot change their name/metadata
# to prevent identity spoofing in the LiveKit room.
can_update_metadata = encryption_mode == 'none' or user.is_anonymous
video_grants = VideoGrants(
room=room,
room_join=True,
room_admin=is_admin_or_owner,
can_update_own_metadata=True,
can_update_own_metadata=can_update_metadata,
can_publish=bool(sources),
can_publish_sources=sources,
can_subscribe=True,
@@ -150,6 +155,7 @@ def generate_livekit_config(
color: Optional[str] = None,
configuration: Optional[dict] = None,
participant_id: Optional[str] = None,
encryption_mode: str = 'none',
) -> dict:
"""Generate LiveKit configuration for room access.
@@ -182,6 +188,7 @@ def generate_livekit_config(
sources=sources,
is_admin_or_owner=is_admin_or_owner,
participant_id=participant_id,
encryption_mode=encryption_mode,
),
}
+1 -1
View File
@@ -561,7 +561,7 @@ class Base(Configuration):
"returnTo", environ_name="OIDC_REDIRECT_FIELD_NAME", environ_prefix=None
)
OIDC_USERINFO_FULLNAME_FIELDS = values.ListValue(
default=["given_name", "usual_name"],
default=["given_name", "usual_name", "family_name"],
environ_name="OIDC_USERINFO_FULLNAME_FIELDS",
environ_prefix=None,
)