Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Port the ThirdPartyEventRules module interface to the new generic interface #10386

Merged
merged 14 commits into from
Jul 20, 2021
Merged
1 change: 1 addition & 0 deletions changelog.d/10386.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The third-party event rules module interface is deprecated in favour of the generic module interface introduced in Synapse v1.37.0. See the [upgrade notes](https://matrix-org.github.io/synapse/latest/upgrade.html#upgrading-to-v1390) for more information.
57 changes: 56 additions & 1 deletion docs/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,68 @@ The arguments passed to this callback are:
```python
async def check_media_file_for_spam(
file_wrapper: "synapse.rest.media.v1.media_storage.ReadableFileWrapper",
file_info: "synapse.rest.media.v1._base.FileInfo"
file_info: "synapse.rest.media.v1._base.FileInfo",
) -> bool
```

Called when storing a local or remote file. The module must return a boolean indicating
whether the given file can be stored in the homeserver's media store.

#### Third party rules callbacks

Third party rules callbacks allow modules developers to add extra checks to verify the
babolivier marked this conversation as resolved.
Show resolved Hide resolved
validity of incoming events. Third party event rules callbacks can be registered using
the module API's `register_third_party_rules_callbacks` method.

The available third party rules callbacks are:

```python
async def check_event_allowed(
event: "synapse.events.EventBase",
state_events: "synapse.types.StateMap",
) -> Tuple[bool, Optional[dict]]
```

Called when processing any incoming event, with the event and the a `StateMap`
babolivier marked this conversation as resolved.
Show resolved Hide resolved
representing the current state of the room the event is being sent into. A `StateMap` is
a dictionary indexed on tuples containing an event type and a state key; for example
babolivier marked this conversation as resolved.
Show resolved Hide resolved
retrieving the room's `m.room.create` event from the `state_events` argument looks like
this: `state_events.get(("m.room.create", ""))`. The module must return a boolean
indicating whether the event can be allowed.

Note that this callback function processes incoming events coming via federation
traffic (on top of client traffic). This means denying an event might cause the local
copy of the room's history to diverge from the ones of remote servers. This may cause
babolivier marked this conversation as resolved.
Show resolved Hide resolved
federation issues in the room. It is strongly recommended to only deny events using this
callback function if the sender is a local user, or in a private federation in which all
servers are using the same module, with the same configuration.
babolivier marked this conversation as resolved.
Show resolved Hide resolved

If the boolean returned by the module is `True`, it may also tell Synapse to replace the
event with new data by returning the new event's data as a dictionary. In order to do
babolivier marked this conversation as resolved.
Show resolved Hide resolved
that, it is recommended the module calls `event.get_dict()` to get the current event as a
dictionary, and modify the returned dictionary accordingly.

Note that replacing the event only works for events sent by local users, not for events
received over federation.

```python
async def on_create_room(
requester: "synapse.types.Requester",
request_content: dict,
is_requester_admin: bool,
) -> None
```

Called when processing a room creation request, with the `Requester` object for the user
performing the request, a dictionary representing the room creation request's JSON body
(see [the spec](https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-createroom)
for a list of possible parameters), and a boolean indicating whether the user performing
the request is a server admin.

Modules can modify the `request_content` (by e.g. adding events to its `initial_state`),
or deny the room's creation by raising a `module_api.errors.SynapseError`.


### Porting an existing module that uses the old interface

In order to port a module that uses Synapse's old module interface, its author needs to:
Expand Down
13 changes: 13 additions & 0 deletions docs/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ process, for example:
```


# Upgrading to v1.39.0

## Deprecation of the current third-party rules module interface

The current third-party rules module interface is deprecated in favour of the new generic
modules system introduced in Synapse v1.37.0. Authors of third-party rules modules can refer
to [this documentation](modules.md#porting-an-existing-module-that-uses-the-old-interface)
to update their modules. Synapse administrators can refer to [this documentation](modules.md#using-modules)
to update their configuration once the modules they are using have been updated.

We plan to remove support for the current spam checker interface in September 2021.
babolivier marked this conversation as resolved.
Show resolved Hide resolved


# Upgrading to v1.38.0

## Re-indexing of `events` table on Postgres databases
Expand Down
11 changes: 6 additions & 5 deletions synapse/events/third_party_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


CHECK_EVENT_ALLOWED_CALLBACK = Callable[
[EventBase, EventContext], Awaitable[Tuple[bool, Optional[dict]]]
[EventBase, StateMap[EventBase]], Awaitable[Tuple[bool, Optional[dict]]]
]
ON_CREATE_ROOM_CALLBACK = Callable[[Requester, dict, bool], Awaitable]
CHECK_THREEPID_CAN_BE_INVITED_CALLBACK = Callable[
Expand Down Expand Up @@ -66,6 +66,7 @@ def async_wrapper(f: Optional[Callable]) -> Optional[Callable[..., Awaitable]]:
# correctly, we need to await its result. Therefore it doesn't make a lot of
# sense to make it go through the run() wrapper.
if f.__name__ == "check_event_allowed":

async def wrapper(
event: EventBase,
state_events: StateMap[EventBase],
Expand Down Expand Up @@ -216,13 +217,13 @@ async def check_event_allowed(
event.freeze()

for callback in self._check_event_allowed_callbacks:
res, new_content = await callback(event, state_events)
res, replacement_data = await callback(event, state_events)
# Return if the event shouldn't be allowed or if the module came up with a
# replacement content for the event.
# replacement dict for the event.
if res is False:
return res, None
elif isinstance(new_content, dict):
return True, new_content
elif isinstance(replacement_data, dict):
return True, replacement_data

return True, None

Expand Down