Allow events to be created with no `prev_events` (MSC2716) (#11243)
The event still needs to have `auth_events` defined to be valid. Split out from https://github.com/matrix-org/synapse/pull/11114pull/11565/head
parent
8391bd6ab5
commit
aa8708ebed
|
@ -0,0 +1 @@
|
||||||
|
Allow specific, experimental events to be created without `prev_events`. Used by [MSC2716](https://github.com/matrix-org/matrix-doc/pull/2716).
|
|
@ -496,6 +496,7 @@ class EventCreationHandler:
|
||||||
require_consent: bool = True,
|
require_consent: bool = True,
|
||||||
outlier: bool = False,
|
outlier: bool = False,
|
||||||
historical: bool = False,
|
historical: bool = False,
|
||||||
|
allow_no_prev_events: bool = False,
|
||||||
depth: Optional[int] = None,
|
depth: Optional[int] = None,
|
||||||
) -> Tuple[EventBase, EventContext]:
|
) -> Tuple[EventBase, EventContext]:
|
||||||
"""
|
"""
|
||||||
|
@ -607,6 +608,7 @@ class EventCreationHandler:
|
||||||
prev_event_ids=prev_event_ids,
|
prev_event_ids=prev_event_ids,
|
||||||
auth_event_ids=auth_event_ids,
|
auth_event_ids=auth_event_ids,
|
||||||
depth=depth,
|
depth=depth,
|
||||||
|
allow_no_prev_events=allow_no_prev_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
# In an ideal world we wouldn't need the second part of this condition. However,
|
# In an ideal world we wouldn't need the second part of this condition. However,
|
||||||
|
@ -882,6 +884,7 @@ class EventCreationHandler:
|
||||||
prev_event_ids: Optional[List[str]] = None,
|
prev_event_ids: Optional[List[str]] = None,
|
||||||
auth_event_ids: Optional[List[str]] = None,
|
auth_event_ids: Optional[List[str]] = None,
|
||||||
depth: Optional[int] = None,
|
depth: Optional[int] = None,
|
||||||
|
allow_no_prev_events: bool = False,
|
||||||
) -> Tuple[EventBase, EventContext]:
|
) -> Tuple[EventBase, EventContext]:
|
||||||
"""Create a new event for a local client
|
"""Create a new event for a local client
|
||||||
|
|
||||||
|
@ -912,6 +915,7 @@ class EventCreationHandler:
|
||||||
full_state_ids_at_event = None
|
full_state_ids_at_event = None
|
||||||
if auth_event_ids is not None:
|
if auth_event_ids is not None:
|
||||||
# If auth events are provided, prev events must be also.
|
# If auth events are provided, prev events must be also.
|
||||||
|
# prev_event_ids could be an empty array though.
|
||||||
assert prev_event_ids is not None
|
assert prev_event_ids is not None
|
||||||
|
|
||||||
# Copy the full auth state before it stripped down
|
# Copy the full auth state before it stripped down
|
||||||
|
@ -943,14 +947,22 @@ class EventCreationHandler:
|
||||||
else:
|
else:
|
||||||
prev_event_ids = await self.store.get_prev_events_for_room(builder.room_id)
|
prev_event_ids = await self.store.get_prev_events_for_room(builder.room_id)
|
||||||
|
|
||||||
# we now ought to have some prev_events (unless it's a create event).
|
# Do a quick sanity check here, rather than waiting until we've created the
|
||||||
#
|
|
||||||
# do a quick sanity check here, rather than waiting until we've created the
|
|
||||||
# event and then try to auth it (which fails with a somewhat confusing "No
|
# event and then try to auth it (which fails with a somewhat confusing "No
|
||||||
# create event in auth events")
|
# create event in auth events")
|
||||||
|
if allow_no_prev_events:
|
||||||
|
# We allow events with no `prev_events` but it better have some `auth_events`
|
||||||
assert (
|
assert (
|
||||||
builder.type == EventTypes.Create or len(prev_event_ids) > 0
|
builder.type == EventTypes.Create
|
||||||
), "Attempting to create an event with no prev_events"
|
# Allow an event to have empty list of prev_event_ids
|
||||||
|
# only if it has auth_event_ids.
|
||||||
|
or auth_event_ids
|
||||||
|
), "Attempting to create a non-m.room.create event with no prev_events or auth_event_ids"
|
||||||
|
else:
|
||||||
|
# we now ought to have some prev_events (unless it's a create event).
|
||||||
|
assert (
|
||||||
|
builder.type == EventTypes.Create or prev_event_ids
|
||||||
|
), "Attempting to create a non-m.room.create event with no prev_events"
|
||||||
|
|
||||||
event = await builder.build(
|
event = await builder.build(
|
||||||
prev_event_ids=prev_event_ids,
|
prev_event_ids=prev_event_ids,
|
||||||
|
|
|
@ -658,7 +658,8 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
|
||||||
if block_invite:
|
if block_invite:
|
||||||
raise SynapseError(403, "Invites have been disabled on this server")
|
raise SynapseError(403, "Invites have been disabled on this server")
|
||||||
|
|
||||||
if prev_event_ids:
|
# An empty prev_events list is allowed as long as the auth_event_ids are present
|
||||||
|
if prev_event_ids is not None:
|
||||||
return await self._local_membership_update(
|
return await self._local_membership_update(
|
||||||
requester=requester,
|
requester=requester,
|
||||||
target=target,
|
target=target,
|
||||||
|
|
|
@ -23,6 +23,7 @@ from synapse.types import create_requester
|
||||||
from synapse.util.stringutils import random_string
|
from synapse.util.stringutils import random_string
|
||||||
|
|
||||||
from tests import unittest
|
from tests import unittest
|
||||||
|
from tests.test_utils.event_injection import create_event
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -51,6 +52,24 @@ class EventCreationTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
self.requester = create_requester(self.user_id, access_token_id=self.token_id)
|
self.requester = create_requester(self.user_id, access_token_id=self.token_id)
|
||||||
|
|
||||||
|
def _create_and_persist_member_event(self) -> Tuple[EventBase, EventContext]:
|
||||||
|
# Create a member event we can use as an auth_event
|
||||||
|
memberEvent, memberEventContext = self.get_success(
|
||||||
|
create_event(
|
||||||
|
self.hs,
|
||||||
|
room_id=self.room_id,
|
||||||
|
type="m.room.member",
|
||||||
|
sender=self.requester.user.to_string(),
|
||||||
|
state_key=self.requester.user.to_string(),
|
||||||
|
content={"membership": "join"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.get_success(
|
||||||
|
self.persist_event_storage.persist_event(memberEvent, memberEventContext)
|
||||||
|
)
|
||||||
|
|
||||||
|
return memberEvent, memberEventContext
|
||||||
|
|
||||||
def _create_duplicate_event(self, txn_id: str) -> Tuple[EventBase, EventContext]:
|
def _create_duplicate_event(self, txn_id: str) -> Tuple[EventBase, EventContext]:
|
||||||
"""Create a new event with the given transaction ID. All events produced
|
"""Create a new event with the given transaction ID. All events produced
|
||||||
by this method will be considered duplicates.
|
by this method will be considered duplicates.
|
||||||
|
@ -156,6 +175,90 @@ class EventCreationTestCase(unittest.HomeserverTestCase):
|
||||||
self.assertEqual(len(events), 2)
|
self.assertEqual(len(events), 2)
|
||||||
self.assertEqual(events[0].event_id, events[1].event_id)
|
self.assertEqual(events[0].event_id, events[1].event_id)
|
||||||
|
|
||||||
|
def test_when_empty_prev_events_allowed_create_event_with_empty_prev_events(self):
|
||||||
|
"""When we set allow_no_prev_events=True, should be able to create a
|
||||||
|
event without any prev_events (only auth_events).
|
||||||
|
"""
|
||||||
|
# Create a member event we can use as an auth_event
|
||||||
|
memberEvent, _ = self._create_and_persist_member_event()
|
||||||
|
|
||||||
|
# Try to create the event with empty prev_events bit with some auth_events
|
||||||
|
event, _ = self.get_success(
|
||||||
|
self.handler.create_event(
|
||||||
|
self.requester,
|
||||||
|
{
|
||||||
|
"type": EventTypes.Message,
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": self.requester.user.to_string(),
|
||||||
|
"content": {"msgtype": "m.text", "body": random_string(5)},
|
||||||
|
},
|
||||||
|
# Empty prev_events is the key thing we're testing here
|
||||||
|
prev_event_ids=[],
|
||||||
|
# But with some auth_events
|
||||||
|
auth_event_ids=[memberEvent.event_id],
|
||||||
|
# Allow no prev_events!
|
||||||
|
allow_no_prev_events=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(event)
|
||||||
|
|
||||||
|
def test_when_empty_prev_events_not_allowed_reject_event_with_empty_prev_events(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
"""When we set allow_no_prev_events=False, shouldn't be able to create a
|
||||||
|
event without any prev_events even if it has auth_events. Expect an
|
||||||
|
exception to be raised.
|
||||||
|
"""
|
||||||
|
# Create a member event we can use as an auth_event
|
||||||
|
memberEvent, _ = self._create_and_persist_member_event()
|
||||||
|
|
||||||
|
# Try to create the event with empty prev_events but with some auth_events
|
||||||
|
self.get_failure(
|
||||||
|
self.handler.create_event(
|
||||||
|
self.requester,
|
||||||
|
{
|
||||||
|
"type": EventTypes.Message,
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": self.requester.user.to_string(),
|
||||||
|
"content": {"msgtype": "m.text", "body": random_string(5)},
|
||||||
|
},
|
||||||
|
# Empty prev_events is the key thing we're testing here
|
||||||
|
prev_event_ids=[],
|
||||||
|
# But with some auth_events
|
||||||
|
auth_event_ids=[memberEvent.event_id],
|
||||||
|
# We expect the test to fail because empty prev_events are not
|
||||||
|
# allowed here!
|
||||||
|
allow_no_prev_events=False,
|
||||||
|
),
|
||||||
|
AssertionError,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_when_empty_prev_events_allowed_reject_event_with_empty_prev_events_and_auth_events(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
"""When we set allow_no_prev_events=True, should be able to create a
|
||||||
|
event without any prev_events or auth_events. Expect an exception to be
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
# Try to create the event with empty prev_events and empty auth_events
|
||||||
|
self.get_failure(
|
||||||
|
self.handler.create_event(
|
||||||
|
self.requester,
|
||||||
|
{
|
||||||
|
"type": EventTypes.Message,
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"sender": self.requester.user.to_string(),
|
||||||
|
"content": {"msgtype": "m.text", "body": random_string(5)},
|
||||||
|
},
|
||||||
|
prev_event_ids=[],
|
||||||
|
# The event should be rejected when there are no auth_events
|
||||||
|
auth_event_ids=[],
|
||||||
|
# Allow no prev_events!
|
||||||
|
allow_no_prev_events=True,
|
||||||
|
),
|
||||||
|
AssertionError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ServerAclValidationTestCase(unittest.HomeserverTestCase):
|
class ServerAclValidationTestCase(unittest.HomeserverTestCase):
|
||||||
servlets = [
|
servlets = [
|
||||||
|
|
Loading…
Reference in New Issue