summary refs log tree commit diff
path: root/synapse/handlers/message.py
diff options
context:
space:
mode:
authorRichard van der Hoff <richard@matrix.org>2020-10-13 18:53:56 +0100
committerRichard van der Hoff <richard@matrix.org>2020-10-13 23:24:50 +0100
commit617e8a46538af56bd5abbb2c9e4df8025841c338 (patch)
tree80335f25c52b7b240d33fc32570d59d6c1ab1ff7 /synapse/handlers/message.py
parentMove third_party_rules check to event creation time (diff)
downloadsynapse-617e8a46538af56bd5abbb2c9e4df8025841c338.tar.xz
Allow ThirdPartyRules modules to replace event content
Support returning a new event dict from `check_event_allowed`.
Diffstat (limited to 'synapse/handlers/message.py')
-rw-r--r--synapse/handlers/message.py64
1 files changed, 62 insertions, 2 deletions
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 987c759791..0c6aec347e 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -795,16 +795,22 @@ class EventCreationHandler:
         if requester:
             context.app_service = requester.app_service
 
-        event_allowed = await self.third_party_event_rules.check_event_allowed(
+        third_party_result = await self.third_party_event_rules.check_event_allowed(
             event, context
         )
-        if not event_allowed:
+        if not third_party_result:
             logger.info(
                 "Event %s forbidden by third-party rules", event,
             )
             raise SynapseError(
                 403, "This event is not allowed in this context", Codes.FORBIDDEN
             )
+        elif isinstance(third_party_result, dict):
+            # the third-party rules want to replace the event. We'll need to build a new
+            # event.
+            event, context = await self._rebuild_event_after_third_party_rules(
+                third_party_result, event
+            )
 
         self.validator.validate_new(event, self.config)
 
@@ -1294,3 +1300,57 @@ class EventCreationHandler:
                 room_id,
             )
             del self._rooms_to_exclude_from_dummy_event_insertion[room_id]
+
+    async def _rebuild_event_after_third_party_rules(
+        self, third_party_result: dict, original_event: EventBase
+    ) -> Tuple[EventBase, EventContext]:
+        # the third_party_event_rules want to replace the event.
+        # we do some basic checks, and then return the replacement event and context.
+
+        # Construct a new EventBuilder and validate it, which helps with the
+        # rest of these checks.
+        try:
+            builder = self.event_builder_factory.for_room_version(
+                original_event.room_version, third_party_result
+            )
+            self.validator.validate_builder(builder)
+        except SynapseError as e:
+            raise Exception(
+                "Third party rules module created an invalid event: " + e.msg,
+            )
+
+        immutable_fields = [
+            # changing the room is going to break things: we've already checked that the
+            # room exists, and are holding a concurrency limiter token for that room.
+            # Also, we might need to use a different room version.
+            "room_id",
+            # changing the type or state key might work, but we'd need to check that the
+            # calling functions aren't making assumptions about them.
+            "type",
+            "state_key",
+        ]
+
+        for k in immutable_fields:
+            if getattr(builder, k, None) != original_event.get(k):
+                raise Exception(
+                    "Third party rules module created an invalid event: "
+                    "cannot change field " + k
+                )
+
+        # check that the new sender belongs to this HS
+        if not self.hs.is_mine_id(builder.sender):
+            raise Exception(
+                "Third party rules module created an invalid event: "
+                "invalid sender " + builder.sender
+            )
+
+        # copy over the original internal metadata
+        for k, v in original_event.internal_metadata.get_dict().items():
+            setattr(builder.internal_metadata, k, v)
+
+        event = await builder.build(prev_event_ids=original_event.prev_event_ids())
+
+        # we rebuild the event context, to be on the safe side. If nothing else,
+        # delta_ids might need an update.
+        context = await self.state.compute_event_context(event)
+        return event, context