184 lines
7.2 KiB
Markdown
184 lines
7.2 KiB
Markdown
# Third party rules callbacks
|
|
|
|
Third party rules callbacks allow module developers to add extra checks to verify the
|
|
validity of incoming events. Third party event rules callbacks can be registered using
|
|
the module API's `register_third_party_rules_callbacks` method.
|
|
|
|
## Callbacks
|
|
|
|
The available third party rules callbacks are:
|
|
|
|
### `check_event_allowed`
|
|
|
|
_First introduced in Synapse v1.39.0_
|
|
|
|
```python
|
|
async def check_event_allowed(
|
|
event: "synapse.events.EventBase",
|
|
state_events: "synapse.types.StateMap",
|
|
) -> Tuple[bool, Optional[dict]]
|
|
```
|
|
|
|
**<span style="color:red">
|
|
This callback is very experimental and can and will break without notice. Module developers
|
|
are encouraged to implement `check_event_for_spam` from the spam checker category instead.
|
|
</span>**
|
|
|
|
Called when processing any incoming event, with the event and a `StateMap`
|
|
representing the current state of the room the event is being sent into. A `StateMap` is
|
|
a dictionary that maps tuples containing an event type and a state key to the
|
|
corresponding state event. For example retrieving the room's `m.room.create` event from
|
|
the `state_events` argument would look 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 that of remote servers. This may cause
|
|
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.
|
|
|
|
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
|
|
that, it is recommended the module calls `event.get_dict()` to get the current event as a
|
|
dictionary, and modify the returned dictionary accordingly.
|
|
|
|
If `check_event_allowed` raises an exception, the module is assumed to have failed.
|
|
The event will not be accepted but is not treated as explicitly rejected, either.
|
|
An HTTP request causing the module check will likely result in a 500 Internal
|
|
Server Error.
|
|
|
|
When the boolean returned by the module is `False`, the event is rejected.
|
|
(Module developers should not use exceptions for rejection.)
|
|
|
|
Note that replacing the event only works for events sent by local users, not for events
|
|
received over federation.
|
|
|
|
If multiple modules implement this callback, they will be considered in order. If a
|
|
callback returns `True`, Synapse falls through to the next one. The value of the first
|
|
callback that does not return `True` will be used. If this happens, Synapse will not call
|
|
any of the subsequent implementations of this callback.
|
|
|
|
### `on_create_room`
|
|
|
|
_First introduced in Synapse v1.39.0_
|
|
|
|
```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`.
|
|
|
|
If multiple modules implement this callback, they will be considered in order. If a
|
|
callback returns without raising an exception, Synapse falls through to the next one. The
|
|
room creation will be forbidden as soon as one of the callbacks raises an exception. If
|
|
this happens, Synapse will not call any of the subsequent implementations of this
|
|
callback.
|
|
|
|
### `check_threepid_can_be_invited`
|
|
|
|
_First introduced in Synapse v1.39.0_
|
|
|
|
```python
|
|
async def check_threepid_can_be_invited(
|
|
medium: str,
|
|
address: str,
|
|
state_events: "synapse.types.StateMap",
|
|
) -> bool:
|
|
```
|
|
|
|
Called when processing an invite via a third-party identifier (i.e. email or phone number).
|
|
The module must return a boolean indicating whether the invite can go through.
|
|
|
|
If multiple modules implement this callback, they will be considered in order. If a
|
|
callback returns `True`, Synapse falls through to the next one. The value of the first
|
|
callback that does not return `True` will be used. If this happens, Synapse will not call
|
|
any of the subsequent implementations of this callback.
|
|
|
|
### `check_visibility_can_be_modified`
|
|
|
|
_First introduced in Synapse v1.39.0_
|
|
|
|
```python
|
|
async def check_visibility_can_be_modified(
|
|
room_id: str,
|
|
state_events: "synapse.types.StateMap",
|
|
new_visibility: str,
|
|
) -> bool:
|
|
```
|
|
|
|
Called when changing the visibility of a room in the local public room directory. The
|
|
visibility is a string that's either "public" or "private". The module must return a
|
|
boolean indicating whether the change can go through.
|
|
|
|
If multiple modules implement this callback, they will be considered in order. If a
|
|
callback returns `True`, Synapse falls through to the next one. The value of the first
|
|
callback that does not return `True` will be used. If this happens, Synapse will not call
|
|
any of the subsequent implementations of this callback.
|
|
|
|
### `on_new_event`
|
|
|
|
_First introduced in Synapse v1.47.0_
|
|
|
|
```python
|
|
async def on_new_event(
|
|
event: "synapse.events.EventBase",
|
|
state_events: "synapse.types.StateMap",
|
|
) -> None:
|
|
```
|
|
|
|
Called after sending an event into a room. The module is passed the event, as well
|
|
as the state of the room _after_ the event. This means that if the event is a state event,
|
|
it will be included in this state.
|
|
|
|
Note that this callback is called when the event has already been processed and stored
|
|
into the room, which means this callback cannot be used to deny persisting the event. To
|
|
deny an incoming event, see [`check_event_for_spam`](spam_checker_callbacks.md#check_event_for_spam) instead.
|
|
|
|
If multiple modules implement this callback, Synapse runs them all in order.
|
|
|
|
## Example
|
|
|
|
The example below is a module that implements the third-party rules callback
|
|
`check_event_allowed` to censor incoming messages as dictated by a third-party service.
|
|
|
|
```python
|
|
from typing import Optional, Tuple
|
|
|
|
from synapse.module_api import ModuleApi
|
|
|
|
_DEFAULT_CENSOR_ENDPOINT = "https://my-internal-service.local/censor-event"
|
|
|
|
class EventCensorer:
|
|
def __init__(self, config: dict, api: ModuleApi):
|
|
self.api = api
|
|
self._endpoint = config.get("endpoint", _DEFAULT_CENSOR_ENDPOINT)
|
|
|
|
self.api.register_third_party_rules_callbacks(
|
|
check_event_allowed=self.check_event_allowed,
|
|
)
|
|
|
|
async def check_event_allowed(
|
|
self,
|
|
event: "synapse.events.EventBase",
|
|
state_events: "synapse.types.StateMap",
|
|
) -> Tuple[bool, Optional[dict]]:
|
|
event_dict = event.get_dict()
|
|
new_event_content = await self.api.http_client.post_json_get_json(
|
|
uri=self._endpoint, post_json=event_dict,
|
|
)
|
|
event_dict["content"] = new_event_content
|
|
return event_dict
|
|
```
|