Skip to content

Commit 8fb5b0f

Browse files
authored
Improve event validation (#16908)
As the title states.
1 parent 77b8240 commit 8fb5b0f

File tree

6 files changed

+180
-3
lines changed

6 files changed

+180
-3
lines changed

changelog.d/16908.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve event validation (#16908).

synapse/api/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ class EventTypes:
129129

130130
Reaction: Final = "m.reaction"
131131

132+
CallInvite: Final = "m.call.invite"
133+
132134

133135
class ToDeviceEventTypes:
134136
RoomKeyRequest: Final = "m.room_key_request"

synapse/handlers/message.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
EventTypes,
3535
GuestAccess,
3636
HistoryVisibility,
37+
JoinRules,
3738
Membership,
3839
RelationTypes,
3940
UserTypes,
@@ -1325,6 +1326,18 @@ async def create_new_client_event(
13251326

13261327
self.validator.validate_new(event, self.config)
13271328
await self._validate_event_relation(event)
1329+
1330+
if event.type == EventTypes.CallInvite:
1331+
room_id = event.room_id
1332+
room_info = await self.store.get_room_with_stats(room_id)
1333+
assert room_info is not None
1334+
1335+
if room_info.join_rules == JoinRules.PUBLIC:
1336+
raise SynapseError(
1337+
403,
1338+
"Call invites are not allowed in public rooms.",
1339+
Codes.FORBIDDEN,
1340+
)
13281341
logger.debug("Created event %s", event.event_id)
13291342

13301343
return event, context

synapse/handlers/sync.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
AccountDataTypes,
4242
EventContentFields,
4343
EventTypes,
44+
JoinRules,
4445
Membership,
4546
)
4647
from synapse.api.filtering import FilterCollection
@@ -675,13 +676,22 @@ async def _load_filtered_recents(
675676
)
676677
)
677678

678-
loaded_recents = await filter_events_for_client(
679+
filtered_recents = await filter_events_for_client(
679680
self._storage_controllers,
680681
sync_config.user.to_string(),
681682
loaded_recents,
682683
always_include_ids=current_state_ids,
683684
)
684685

686+
loaded_recents = []
687+
for event in filtered_recents:
688+
if event.type == EventTypes.CallInvite:
689+
room_info = await self.store.get_room_with_stats(event.room_id)
690+
assert room_info is not None
691+
if room_info.join_rules == JoinRules.PUBLIC:
692+
continue
693+
loaded_recents.append(event)
694+
685695
log_kv({"loaded_recents_after_client_filtering": len(loaded_recents)})
686696

687697
loaded_recents.extend(recents)

tests/handlers/test_message.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from twisted.test.proto_helpers import MemoryReactor
2525

2626
from synapse.api.constants import EventTypes
27+
from synapse.api.errors import SynapseError
2728
from synapse.events import EventBase
2829
from synapse.events.snapshot import EventContext, UnpersistedEventContextBase
2930
from synapse.rest import admin
@@ -51,11 +52,15 @@ def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
5152
persistence = self.hs.get_storage_controllers().persistence
5253
assert persistence is not None
5354
self._persist_event_storage_controller = persistence
55+
self.store = self.hs.get_datastores().main
5456

5557
self.user_id = self.register_user("tester", "foobar")
5658
device_id = "dev-1"
5759
access_token = self.login("tester", "foobar", device_id=device_id)
5860
self.room_id = self.helper.create_room_as(self.user_id, tok=access_token)
61+
self.private_room_id = self.helper.create_room_as(
62+
self.user_id, tok=access_token, extra_content={"preset": "private_chat"}
63+
)
5964

6065
self.requester = create_requester(self.user_id, device_id=device_id)
6166

@@ -285,6 +290,41 @@ def test_when_empty_prev_events_allowed_reject_event_with_empty_prev_events_and_
285290
AssertionError,
286291
)
287292

293+
def test_call_invite_event_creation_fails_in_public_room(self) -> None:
294+
# get prev_events for room
295+
prev_events = self.get_success(
296+
self.store.get_prev_events_for_room(self.room_id)
297+
)
298+
299+
# the invite in a public room should fail
300+
self.get_failure(
301+
self.handler.create_event(
302+
self.requester,
303+
{
304+
"type": EventTypes.CallInvite,
305+
"room_id": self.room_id,
306+
"sender": self.requester.user.to_string(),
307+
},
308+
prev_event_ids=prev_events,
309+
auth_event_ids=prev_events,
310+
),
311+
SynapseError,
312+
)
313+
314+
# but a call invite in a private room should succeed
315+
self.get_success(
316+
self.handler.create_event(
317+
self.requester,
318+
{
319+
"type": EventTypes.CallInvite,
320+
"room_id": self.private_room_id,
321+
"sender": self.requester.user.to_string(),
322+
},
323+
prev_event_ids=prev_events,
324+
auth_event_ids=prev_events,
325+
)
326+
)
327+
288328

289329
class ServerAclValidationTestCase(unittest.HomeserverTestCase):
290330
servlets = [

tests/handlers/test_sync.py

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@
1717
# [This file includes modifications made by New Vector Limited]
1818
#
1919
#
20-
from typing import Optional
20+
from typing import Collection, List, Optional
2121
from unittest.mock import AsyncMock, Mock, patch
2222

2323
from twisted.test.proto_helpers import MemoryReactor
2424

2525
from synapse.api.constants import EventTypes, JoinRules
2626
from synapse.api.errors import Codes, ResourceLimitError
2727
from synapse.api.filtering import Filtering
28-
from synapse.api.room_versions import RoomVersions
28+
from synapse.api.room_versions import RoomVersion, RoomVersions
29+
from synapse.events import EventBase
30+
from synapse.events.snapshot import EventContext
31+
from synapse.federation.federation_base import event_from_pdu_json
2932
from synapse.handlers.sync import SyncConfig, SyncResult
3033
from synapse.rest import admin
3134
from synapse.rest.client import knock, login, room
@@ -285,6 +288,114 @@ def test_ban_wins_race_with_join(self) -> None:
285288
)
286289
self.assertEqual(eve_initial_sync_after_join.joined, [])
287290

291+
def test_call_invite_in_public_room_not_returned(self) -> None:
292+
user = self.register_user("alice", "password")
293+
tok = self.login(user, "password")
294+
room_id = self.helper.create_room_as(user, is_public=True, tok=tok)
295+
self.handler = self.hs.get_federation_handler()
296+
federation_event_handler = self.hs.get_federation_event_handler()
297+
298+
async def _check_event_auth(
299+
origin: Optional[str], event: EventBase, context: EventContext
300+
) -> None:
301+
pass
302+
303+
federation_event_handler._check_event_auth = _check_event_auth # type: ignore[method-assign]
304+
self.client = self.hs.get_federation_client()
305+
306+
async def _check_sigs_and_hash_for_pulled_events_and_fetch(
307+
dest: str, pdus: Collection[EventBase], room_version: RoomVersion
308+
) -> List[EventBase]:
309+
return list(pdus)
310+
311+
self.client._check_sigs_and_hash_for_pulled_events_and_fetch = _check_sigs_and_hash_for_pulled_events_and_fetch # type: ignore[assignment]
312+
313+
prev_events = self.get_success(self.store.get_prev_events_for_room(room_id))
314+
315+
# create a call invite event
316+
call_event = event_from_pdu_json(
317+
{
318+
"type": EventTypes.CallInvite,
319+
"content": {},
320+
"room_id": room_id,
321+
"sender": user,
322+
"depth": 32,
323+
"prev_events": prev_events,
324+
"auth_events": prev_events,
325+
"origin_server_ts": self.clock.time_msec(),
326+
},
327+
RoomVersions.V10,
328+
)
329+
330+
self.assertEqual(
331+
self.get_success(
332+
federation_event_handler.on_receive_pdu("test.serv", call_event)
333+
),
334+
None,
335+
)
336+
337+
# check that it is in DB
338+
recent_event = self.get_success(self.store.get_prev_events_for_room(room_id))
339+
self.assertIn(call_event.event_id, recent_event)
340+
341+
# but that it does not come down /sync in public room
342+
sync_result: SyncResult = self.get_success(
343+
self.sync_handler.wait_for_sync_for_user(
344+
create_requester(user), generate_sync_config(user)
345+
)
346+
)
347+
event_ids = []
348+
for event in sync_result.joined[0].timeline.events:
349+
event_ids.append(event.event_id)
350+
self.assertNotIn(call_event.event_id, event_ids)
351+
352+
# it will come down in a private room, though
353+
user2 = self.register_user("bob", "password")
354+
tok2 = self.login(user2, "password")
355+
private_room_id = self.helper.create_room_as(
356+
user2, is_public=False, tok=tok2, extra_content={"preset": "private_chat"}
357+
)
358+
359+
priv_prev_events = self.get_success(
360+
self.store.get_prev_events_for_room(private_room_id)
361+
)
362+
private_call_event = event_from_pdu_json(
363+
{
364+
"type": EventTypes.CallInvite,
365+
"content": {},
366+
"room_id": private_room_id,
367+
"sender": user,
368+
"depth": 32,
369+
"prev_events": priv_prev_events,
370+
"auth_events": priv_prev_events,
371+
"origin_server_ts": self.clock.time_msec(),
372+
},
373+
RoomVersions.V10,
374+
)
375+
376+
self.assertEqual(
377+
self.get_success(
378+
federation_event_handler.on_receive_pdu("test.serv", private_call_event)
379+
),
380+
None,
381+
)
382+
383+
recent_events = self.get_success(
384+
self.store.get_prev_events_for_room(private_room_id)
385+
)
386+
self.assertIn(private_call_event.event_id, recent_events)
387+
388+
private_sync_result: SyncResult = self.get_success(
389+
self.sync_handler.wait_for_sync_for_user(
390+
create_requester(user2), generate_sync_config(user2)
391+
)
392+
)
393+
priv_event_ids = []
394+
for event in private_sync_result.joined[0].timeline.events:
395+
priv_event_ids.append(event.event_id)
396+
397+
self.assertIn(private_call_event.event_id, priv_event_ids)
398+
288399

289400
_request_key = 0
290401

0 commit comments

Comments
 (0)