Skip to content

Commit b73ff99

Browse files
committed
✨(backend) force ProConnect official name for authenticated users in trusted rooms
When a room has trusted access level and the user is authenticated, override the username with the user's full_name from ProConnect claims to prevent identity fraud. Falls back to email if full_name is empty. Closes #784
1 parent 0cb3fb8 commit b73ff99

4 files changed

Lines changed: 103 additions & 3 deletions

File tree

src/backend/core/api/serializers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ def to_representation(self, instance):
172172
if should_access_room:
173173
room_id = f"{instance.id!s}"
174174
username = request.query_params.get("username", None)
175+
176+
# Force official identity for authenticated users in trusted rooms
177+
if (
178+
instance.access_level == models.RoomAccessLevel.TRUSTED
179+
and request.user.is_authenticated
180+
):
181+
username = request.user.full_name or str(request.user)
182+
175183
output["livekit"] = utils.generate_livekit_config(
176184
room_id=room_id,
177185
user=request.user,

src/backend/core/services/lobby.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ def request_entry(
146146
room_id = str(room.id)
147147

148148
if self.can_bypass_lobby(room=room, user=request.user):
149+
# Force official identity for authenticated users in trusted rooms
150+
if (
151+
room.access_level == models.RoomAccessLevel.TRUSTED
152+
and request.user.is_authenticated
153+
):
154+
username = request.user.full_name or str(request.user)
155+
149156
if participant is None:
150157
participant = LobbyParticipant(
151158
status=LobbyParticipantStatus.ACCEPTED,
@@ -176,6 +183,13 @@ def request_entry(
176183
self.refresh_waiting_status(room.id, participant_id)
177184

178185
elif participant.status == LobbyParticipantStatus.ACCEPTED:
186+
# Force official identity for authenticated users in trusted rooms
187+
if (
188+
room.access_level == models.RoomAccessLevel.TRUSTED
189+
and request.user.is_authenticated
190+
):
191+
username = request.user.full_name or str(request.user)
192+
179193
# wrongly named, contains access token to join a room
180194
livekit_config = utils.generate_livekit_config(
181195
room_id=room_id,

src/backend/core/tests/rooms/test_api_rooms_retrieve.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ def test_api_rooms_retrieve_authenticated_trusted(mock_token):
283283
Authenticated users should be allowed to retrieve a room and get a token for a room to
284284
which they are not related, provided the room has a trusted access_level.
285285
They should not see related users.
286+
The username should be forced to the user's official name.
286287
"""
287288
room = RoomFactory(access_level=RoomAccessLevel.TRUSTED)
288289

@@ -310,10 +311,84 @@ def test_api_rooms_retrieve_authenticated_trusted(mock_token):
310311
"slug": room.slug,
311312
}
312313

314+
# Username is forced to user's full_name in trusted rooms
313315
mock_token.assert_called_once_with(
314316
room=expected_name,
315317
user=user,
316-
username=None,
318+
username=user.full_name,
319+
color=None,
320+
sources=None,
321+
is_admin_or_owner=False,
322+
participant_id=None,
323+
)
324+
325+
326+
@mock.patch("core.utils.generate_token", return_value="foo")
327+
@override_settings(
328+
LIVEKIT_CONFIGURATION={
329+
"api_key": "key",
330+
"api_secret": "secret",
331+
"url": "test_url_value",
332+
}
333+
)
334+
def test_api_rooms_retrieve_authenticated_trusted_forces_official_name(mock_token):
335+
"""
336+
When a room has trusted access level and the user is authenticated,
337+
the username should be forced to the user's official ProConnect name
338+
(full_name), ignoring any custom username passed as query parameter.
339+
"""
340+
room = RoomFactory(access_level=RoomAccessLevel.TRUSTED)
341+
342+
user = UserFactory(full_name="Jean Dupont")
343+
client = APIClient()
344+
client.force_login(user)
345+
346+
response = client.get(
347+
f"/api/v1.0/rooms/{room.id!s}/?username=FakeIdentity",
348+
)
349+
assert response.status_code == 200
350+
351+
expected_name = f"{room.id!s}"
352+
mock_token.assert_called_once_with(
353+
room=expected_name,
354+
user=user,
355+
username="Jean Dupont",
356+
color=None,
357+
sources=None,
358+
is_admin_or_owner=False,
359+
participant_id=None,
360+
)
361+
362+
363+
@mock.patch("core.utils.generate_token", return_value="foo")
364+
@override_settings(
365+
LIVEKIT_CONFIGURATION={
366+
"api_key": "key",
367+
"api_secret": "secret",
368+
"url": "test_url_value",
369+
}
370+
)
371+
def test_api_rooms_retrieve_authenticated_trusted_fallback_to_email(mock_token):
372+
"""
373+
When a room has trusted access level and the user is authenticated but has no
374+
full_name, the username should fall back to str(user) (email).
375+
"""
376+
room = RoomFactory(access_level=RoomAccessLevel.TRUSTED)
377+
378+
user = UserFactory(full_name="")
379+
client = APIClient()
380+
client.force_login(user)
381+
382+
response = client.get(
383+
f"/api/v1.0/rooms/{room.id!s}/?username=FakeIdentity",
384+
)
385+
assert response.status_code == 200
386+
387+
expected_name = f"{room.id!s}"
388+
mock_token.assert_called_once_with(
389+
room=expected_name,
390+
user=user,
391+
username=str(user),
317392
color=None,
318393
sources=None,
319394
is_admin_or_owner=False,

src/backend/core/tests/services/test_lobby.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,12 @@ def test_request_entry_public_room(
277277
def test_request_entry_trusted_room(
278278
mock_generate_config, lobby_service, participant_id, username
279279
):
280-
"""Test requesting entry to a trusted room when the user is authenticated."""
280+
"""Test requesting entry to a trusted room when the user is authenticated.
281+
The username should be forced to the user's official name."""
281282
request = mock.Mock()
282283
request.user = mock.Mock()
283284
request.user.is_authenticated = True
285+
request.user.full_name = "Official Name"
284286

285287
room = RoomFactory(access_level=RoomAccessLevel.TRUSTED)
286288

@@ -299,10 +301,11 @@ def test_request_entry_trusted_room(
299301

300302
assert participant.status == LobbyParticipantStatus.ACCEPTED
301303
assert livekit_config == {"token": "test-token"}
304+
# Username is forced to the official name in trusted rooms
302305
mock_generate_config.assert_called_once_with(
303306
room_id=str(room.id),
304307
user=request.user,
305-
username=username,
308+
username="Official Name",
306309
color=participant.color,
307310
configuration=room.configuration,
308311
is_admin_or_owner=False,

0 commit comments

Comments
 (0)