summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--synapse/events/third_party_rules.py101
1 files changed, 70 insertions, 31 deletions
diff --git a/synapse/events/third_party_rules.py b/synapse/events/third_party_rules.py
index 97c61cc258..e7532a568a 100644
--- a/synapse/events/third_party_rules.py
+++ b/synapse/events/third_party_rules.py
@@ -32,6 +32,10 @@ logger = logging.getLogger(__name__)
 CHECK_EVENT_ALLOWED_CALLBACK = Callable[
     [EventBase, StateMap[EventBase]], Awaitable[Tuple[bool, Optional[dict]]]
 ]
+CHECK_EVENT_ALLOWED_V2_CALLBACK = Callable[
+    [EventBase, StateMap[EventBase]],
+    Awaitable[Tuple[bool, Optional[dict], Optional[dict]]],
+]
 ON_CREATE_ROOM_CALLBACK = Callable[[Requester, dict, bool], Awaitable]
 CHECK_THREEPID_CAN_BE_INVITED_CALLBACK = Callable[
     [str, str, StateMap[EventBase]], Awaitable[bool]
@@ -155,6 +159,9 @@ class ThirdPartyEventRules:
         self._storage_controllers = hs.get_storage_controllers()
 
         self._check_event_allowed_callbacks: List[CHECK_EVENT_ALLOWED_CALLBACK] = []
+        self._check_event_allowed_v2_callbacks: List[
+            CHECK_EVENT_ALLOWED_V2_CALLBACK
+        ] = []
         self._on_create_room_callbacks: List[ON_CREATE_ROOM_CALLBACK] = []
         self._check_threepid_can_be_invited_callbacks: List[
             CHECK_THREEPID_CAN_BE_INVITED_CALLBACK
@@ -234,7 +241,7 @@ class ThirdPartyEventRules:
         self,
         event: EventBase,
         context: UnpersistedEventContextBase,
-    ) -> Tuple[bool, Optional[dict]]:
+    ) -> Tuple[bool, Optional[dict], Optional[dict]]:
         """Check if a provided event should be allowed in the given context.
 
         The module can return:
@@ -242,7 +249,8 @@ class ThirdPartyEventRules:
             * False: the event is not allowed, and should be rejected with M_FORBIDDEN.
 
         If the event is allowed, the module can also return a dictionary to use as a
-        replacement for the event.
+        replacement for the event, and/or return a dictionary to use as the basis for
+        another event to be sent into the room.
 
         Args:
             event: The event to be checked.
@@ -252,8 +260,11 @@ class ThirdPartyEventRules:
             The result from the ThirdPartyRules module, as above.
         """
         # Bail out early without hitting the store if we don't have any callbacks to run.
-        if len(self._check_event_allowed_callbacks) == 0:
-            return True, None
+        if (
+            len(self._check_event_allowed_callbacks) == 0
+            and len(self._check_event_allowed_v2_callbacks) == 0
+        ):
+            return True, None, None
 
         prev_state_ids = await context.get_prev_state_ids()
 
@@ -266,35 +277,63 @@ class ThirdPartyEventRules:
         # the hashes and signatures.
         event.freeze()
 
-        for callback in self._check_event_allowed_callbacks:
-            try:
-                res, replacement_data = await delay_cancellation(
-                    callback(event, state_events)
-                )
-            except CancelledError:
-                raise
-            except SynapseError as e:
-                # FIXME: Being able to throw SynapseErrors is relied upon by
-                # some modules. PR #10386 accidentally broke this ability.
-                # That said, we aren't keen on exposing this implementation detail
-                # to modules and we should one day have a proper way to do what
-                # is wanted.
-                # This module callback needs a rework so that hacks such as
-                # this one are not necessary.
-                raise e
-            except Exception:
-                raise ModuleFailedException(
-                    "Failed to run `check_event_allowed` module API callback"
-                )
+        if len(self._check_event_allowed_callbacks) != 0:
+            for callback in self._check_event_allowed_callbacks:
+                try:
+                    res, replacement_data = await delay_cancellation(
+                        callback(event, state_events)
+                    )
+                except CancelledError:
+                    raise
+                except SynapseError as e:
+                    # FIXME: Being able to throw SynapseErrors is relied upon by
+                    # some modules. PR #10386 accidentally broke this ability.
+                    # That said, we aren't keen on exposing this implementation detail
+                    # to modules and we should one day have a proper way to do what
+                    # is wanted.
+                    # This module callback needs a rework so that hacks such as
+                    # this one are not necessary.
+                    raise e
+                except Exception:
+                    raise ModuleFailedException(
+                        "Failed to run `check_event_allowed` module API callback"
+                    )
 
-            # Return if the event shouldn't be allowed or if the module came up with a
-            # replacement dict for the event.
-            if res is False:
-                return res, None
-            elif isinstance(replacement_data, dict):
-                return True, replacement_data
+                # Return if the event shouldn't be allowed or if the module came up with a
+                # replacement dict for the event.
+                if res is False:
+                    return res, None, None
+                elif isinstance(replacement_data, dict):
+                    return True, replacement_data, None
+        else:
+            for v2_callback in self._check_event_allowed_v2_callbacks:
+                try:
+                    res, replacement_data, new_event = await delay_cancellation(
+                        v2_callback(event, state_events)
+                    )
+                except CancelledError:
+                    raise
+                except SynapseError as e:
+                    # FIXME: Being able to throw SynapseErrors is relied upon by
+                    # some modules. PR #10386 accidentally broke this ability.
+                    # That said, we aren't keen on exposing this implementation detail
+                    # to modules and we should one day have a proper way to do what
+                    # is wanted.
+                    # This module callback needs a rework so that hacks such as
+                    # this one are not necessary.
+                    raise e
+                except Exception:
+                    raise ModuleFailedException(
+                        "Failed to run `check_event_allowed_v2` module API callback"
+                    )
 
-        return True, None
+                # Return if the event shouldn't be allowed, if the module came up with a
+                # replacement dict for the event, or if the module wants to send a new event
+                if res is False:
+                    return res, None, None
+                else:
+                    return True, replacement_data, new_event
+        return True, None, None
 
     async def on_create_room(
         self, requester: Requester, config: dict, is_requester_admin: bool