0
0
Fork 1
mirror of https://mau.dev/maunium/synapse.git synced 2024-06-01 10:18:54 +02:00

Improve event validation (#16908)

As the title states.
This commit is contained in:
Shay 2024-03-19 10:52:53 -07:00 committed by GitHub
parent 77b824008c
commit 8fb5b0f335
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 180 additions and 3 deletions

1
changelog.d/16908.misc Normal file
View file

@ -0,0 +1 @@
Improve event validation (#16908).

View file

@ -129,6 +129,8 @@ class EventTypes:
Reaction: Final = "m.reaction" Reaction: Final = "m.reaction"
CallInvite: Final = "m.call.invite"
class ToDeviceEventTypes: class ToDeviceEventTypes:
RoomKeyRequest: Final = "m.room_key_request" RoomKeyRequest: Final = "m.room_key_request"

View file

@ -34,6 +34,7 @@ from synapse.api.constants import (
EventTypes, EventTypes,
GuestAccess, GuestAccess,
HistoryVisibility, HistoryVisibility,
JoinRules,
Membership, Membership,
RelationTypes, RelationTypes,
UserTypes, UserTypes,
@ -1325,6 +1326,18 @@ class EventCreationHandler:
self.validator.validate_new(event, self.config) self.validator.validate_new(event, self.config)
await self._validate_event_relation(event) await self._validate_event_relation(event)
if event.type == EventTypes.CallInvite:
room_id = event.room_id
room_info = await self.store.get_room_with_stats(room_id)
assert room_info is not None
if room_info.join_rules == JoinRules.PUBLIC:
raise SynapseError(
403,
"Call invites are not allowed in public rooms.",
Codes.FORBIDDEN,
)
logger.debug("Created event %s", event.event_id) logger.debug("Created event %s", event.event_id)
return event, context return event, context

View file

@ -41,6 +41,7 @@ from synapse.api.constants import (
AccountDataTypes, AccountDataTypes,
EventContentFields, EventContentFields,
EventTypes, EventTypes,
JoinRules,
Membership, Membership,
) )
from synapse.api.filtering import FilterCollection from synapse.api.filtering import FilterCollection
@ -675,13 +676,22 @@ class SyncHandler:
) )
) )
loaded_recents = await filter_events_for_client( filtered_recents = await filter_events_for_client(
self._storage_controllers, self._storage_controllers,
sync_config.user.to_string(), sync_config.user.to_string(),
loaded_recents, loaded_recents,
always_include_ids=current_state_ids, always_include_ids=current_state_ids,
) )
loaded_recents = []
for event in filtered_recents:
if event.type == EventTypes.CallInvite:
room_info = await self.store.get_room_with_stats(event.room_id)
assert room_info is not None
if room_info.join_rules == JoinRules.PUBLIC:
continue
loaded_recents.append(event)
log_kv({"loaded_recents_after_client_filtering": len(loaded_recents)}) log_kv({"loaded_recents_after_client_filtering": len(loaded_recents)})
loaded_recents.extend(recents) loaded_recents.extend(recents)

View file

@ -24,6 +24,7 @@ from typing import Tuple
from twisted.test.proto_helpers import MemoryReactor from twisted.test.proto_helpers import MemoryReactor
from synapse.api.constants import EventTypes from synapse.api.constants import EventTypes
from synapse.api.errors import SynapseError
from synapse.events import EventBase from synapse.events import EventBase
from synapse.events.snapshot import EventContext, UnpersistedEventContextBase from synapse.events.snapshot import EventContext, UnpersistedEventContextBase
from synapse.rest import admin from synapse.rest import admin
@ -51,11 +52,15 @@ class EventCreationTestCase(unittest.HomeserverTestCase):
persistence = self.hs.get_storage_controllers().persistence persistence = self.hs.get_storage_controllers().persistence
assert persistence is not None assert persistence is not None
self._persist_event_storage_controller = persistence self._persist_event_storage_controller = persistence
self.store = self.hs.get_datastores().main
self.user_id = self.register_user("tester", "foobar") self.user_id = self.register_user("tester", "foobar")
device_id = "dev-1" device_id = "dev-1"
access_token = self.login("tester", "foobar", device_id=device_id) access_token = self.login("tester", "foobar", device_id=device_id)
self.room_id = self.helper.create_room_as(self.user_id, tok=access_token) self.room_id = self.helper.create_room_as(self.user_id, tok=access_token)
self.private_room_id = self.helper.create_room_as(
self.user_id, tok=access_token, extra_content={"preset": "private_chat"}
)
self.requester = create_requester(self.user_id, device_id=device_id) self.requester = create_requester(self.user_id, device_id=device_id)
@ -285,6 +290,41 @@ class EventCreationTestCase(unittest.HomeserverTestCase):
AssertionError, AssertionError,
) )
def test_call_invite_event_creation_fails_in_public_room(self) -> None:
# get prev_events for room
prev_events = self.get_success(
self.store.get_prev_events_for_room(self.room_id)
)
# the invite in a public room should fail
self.get_failure(
self.handler.create_event(
self.requester,
{
"type": EventTypes.CallInvite,
"room_id": self.room_id,
"sender": self.requester.user.to_string(),
},
prev_event_ids=prev_events,
auth_event_ids=prev_events,
),
SynapseError,
)
# but a call invite in a private room should succeed
self.get_success(
self.handler.create_event(
self.requester,
{
"type": EventTypes.CallInvite,
"room_id": self.private_room_id,
"sender": self.requester.user.to_string(),
},
prev_event_ids=prev_events,
auth_event_ids=prev_events,
)
)
class ServerAclValidationTestCase(unittest.HomeserverTestCase): class ServerAclValidationTestCase(unittest.HomeserverTestCase):
servlets = [ servlets = [

View file

@ -17,7 +17,7 @@
# [This file includes modifications made by New Vector Limited] # [This file includes modifications made by New Vector Limited]
# #
# #
from typing import Optional from typing import Collection, List, Optional
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch
from twisted.test.proto_helpers import MemoryReactor from twisted.test.proto_helpers import MemoryReactor
@ -25,7 +25,10 @@ from twisted.test.proto_helpers import MemoryReactor
from synapse.api.constants import EventTypes, JoinRules from synapse.api.constants import EventTypes, JoinRules
from synapse.api.errors import Codes, ResourceLimitError from synapse.api.errors import Codes, ResourceLimitError
from synapse.api.filtering import Filtering from synapse.api.filtering import Filtering
from synapse.api.room_versions import RoomVersions from synapse.api.room_versions import RoomVersion, RoomVersions
from synapse.events import EventBase
from synapse.events.snapshot import EventContext
from synapse.federation.federation_base import event_from_pdu_json
from synapse.handlers.sync import SyncConfig, SyncResult from synapse.handlers.sync import SyncConfig, SyncResult
from synapse.rest import admin from synapse.rest import admin
from synapse.rest.client import knock, login, room from synapse.rest.client import knock, login, room
@ -285,6 +288,114 @@ class SyncTestCase(tests.unittest.HomeserverTestCase):
) )
self.assertEqual(eve_initial_sync_after_join.joined, []) self.assertEqual(eve_initial_sync_after_join.joined, [])
def test_call_invite_in_public_room_not_returned(self) -> None:
user = self.register_user("alice", "password")
tok = self.login(user, "password")
room_id = self.helper.create_room_as(user, is_public=True, tok=tok)
self.handler = self.hs.get_federation_handler()
federation_event_handler = self.hs.get_federation_event_handler()
async def _check_event_auth(
origin: Optional[str], event: EventBase, context: EventContext
) -> None:
pass
federation_event_handler._check_event_auth = _check_event_auth # type: ignore[method-assign]
self.client = self.hs.get_federation_client()
async def _check_sigs_and_hash_for_pulled_events_and_fetch(
dest: str, pdus: Collection[EventBase], room_version: RoomVersion
) -> List[EventBase]:
return list(pdus)
self.client._check_sigs_and_hash_for_pulled_events_and_fetch = _check_sigs_and_hash_for_pulled_events_and_fetch # type: ignore[assignment]
prev_events = self.get_success(self.store.get_prev_events_for_room(room_id))
# create a call invite event
call_event = event_from_pdu_json(
{
"type": EventTypes.CallInvite,
"content": {},
"room_id": room_id,
"sender": user,
"depth": 32,
"prev_events": prev_events,
"auth_events": prev_events,
"origin_server_ts": self.clock.time_msec(),
},
RoomVersions.V10,
)
self.assertEqual(
self.get_success(
federation_event_handler.on_receive_pdu("test.serv", call_event)
),
None,
)
# check that it is in DB
recent_event = self.get_success(self.store.get_prev_events_for_room(room_id))
self.assertIn(call_event.event_id, recent_event)
# but that it does not come down /sync in public room
sync_result: SyncResult = self.get_success(
self.sync_handler.wait_for_sync_for_user(
create_requester(user), generate_sync_config(user)
)
)
event_ids = []
for event in sync_result.joined[0].timeline.events:
event_ids.append(event.event_id)
self.assertNotIn(call_event.event_id, event_ids)
# it will come down in a private room, though
user2 = self.register_user("bob", "password")
tok2 = self.login(user2, "password")
private_room_id = self.helper.create_room_as(
user2, is_public=False, tok=tok2, extra_content={"preset": "private_chat"}
)
priv_prev_events = self.get_success(
self.store.get_prev_events_for_room(private_room_id)
)
private_call_event = event_from_pdu_json(
{
"type": EventTypes.CallInvite,
"content": {},
"room_id": private_room_id,
"sender": user,
"depth": 32,
"prev_events": priv_prev_events,
"auth_events": priv_prev_events,
"origin_server_ts": self.clock.time_msec(),
},
RoomVersions.V10,
)
self.assertEqual(
self.get_success(
federation_event_handler.on_receive_pdu("test.serv", private_call_event)
),
None,
)
recent_events = self.get_success(
self.store.get_prev_events_for_room(private_room_id)
)
self.assertIn(private_call_event.event_id, recent_events)
private_sync_result: SyncResult = self.get_success(
self.sync_handler.wait_for_sync_for_user(
create_requester(user2), generate_sync_config(user2)
)
)
priv_event_ids = []
for event in private_sync_result.joined[0].timeline.events:
priv_event_ids.append(event.event_id)
self.assertIn(private_call_event.event_id, priv_event_ids)
_request_key = 0 _request_key = 0