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

Document Synapse's behaviour when dealing with multiple modules #11096

Merged
merged 5 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/11096.doc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Document Synapse's behaviour when dealing with multiple modules registering the same callbacks and/or handlers for the same HTTP endpoints.
7 changes: 7 additions & 0 deletions docs/modules/account_validity_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ If the module returns `True`, the current request will be denied with the error
`ORG_MATRIX_EXPIRED_ACCOUNT` and the HTTP status code 403. Note that this doesn't
invalidate the user's access token.

If multiple modules implement this callback, they will be considered in order. If a
callback returns `None`, Synapse falls through to the next one. The value of the first
callback that does not return `None` will be used. If this happens, Synapse will not call
any of the subsequent implementations of this callback.

### `on_user_registration`

```python
Expand All @@ -31,3 +36,5 @@ async def on_user_registration(user: str) -> None
Called after successfully registering a user, in case the module needs to perform extra
operations to keep track of them. (e.g. add them to a database table). The user is
represented by their Matrix user ID.

If multiple modules implement this callback, Synapse runs them all in order.
33 changes: 26 additions & 7 deletions docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Synapse supports extending its functionality by configuring external modules.

**Note**: When using third-party modules, you effectively allow someone else to run
custom code on your Synapse homeserver. Server admins are encouraged to verify the
provenance of the modules they use on their homeserver and make sure the modules aren't
running malicious code on their instance.

## Using modules

To use a module on Synapse, add it to the `modules` section of the configuration file:
Expand All @@ -18,17 +23,31 @@ modules:
Each module is defined by a path to a Python class as well as a configuration. This
information for a given module should be available in the module's own documentation.

**Note**: When using third-party modules, you effectively allow someone else to run
custom code on your Synapse homeserver. Server admins are encouraged to verify the
provenance of the modules they use on their homeserver and make sure the modules aren't
running malicious code on their instance.
## Using multiple modules

The order in which modules are listed in this section is important. When processing an
action that can be handled by several modules, Synapse will always prioritise the module
that appears first (i.e. is the highest in the list). This means:

* If several modules register the same callback, the callback registered by the module
that appears first is used.
* If several modules try to register a handler for the same HTTP path, only the handler
registered by the module that appears first is used. Handlers registered by the other
module(s) are ignored and Synapse will log a warning message about them.

Note that Synapse doesn't allow multiple modules implementing authentication checkers via
the password auth provider feature for the same login type with different fields. If this
happens, Synapse will refuse to start.

## Current status

Also note that we are currently in the process of migrating module interfaces to this
system. While some interfaces might be compatible with it, others still require
configuring modules in another part of Synapse's configuration file.
We are currently in the process of migrating module interfaces to this system. While some
interfaces might be compatible with it, others still require configuring modules in
another part of Synapse's configuration file.

Currently, only the following pre-existing interfaces are compatible with this new system:

* spam checker
* third-party rules
* presence router
* password auth providers
19 changes: 18 additions & 1 deletion docs/modules/password_auth_provider_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ instead.

If the authentication is unsuccessful, the module must return `None`.

If multiple modules register an auth checker for the same login type but with different
fields, Synapse will refuse to start.

If multiple modules register an auth checker for the same login type with the same fields,
then the callbacks will be executed in order, until one returns a Matrix User ID (and
optionally a callback). In that case, the return value of that callback will be accepted
and subsequent callbacks will not be fired. If every callback returns `None`, then the
authentication fails.

### `check_3pid_auth`

```python
Expand All @@ -67,7 +76,13 @@ If the authentication is successful, the module must return the user's Matrix ID
`@alice:example.com`) and optionally a callback to be called with the response to the `/login` request.
If the module doesn't wish to return a callback, it must return None instead.

If the authentication is unsuccessful, the module must return None.
If the authentication is unsuccessful, the module must return `None`.

If multiple modules implement this callback, they will be considered in order. If a
callback returns `None`, Synapse falls through to the next one. The value of the first
callback that does not return `None` will be used. If this happens, Synapse will not call
any of the subsequent implementations of this callback. If every callback return `None`,
the authentication is denied.

### `on_logged_out`

Expand All @@ -82,6 +97,8 @@ Called during a logout request for a user. It is passed the qualified user ID, t
deactivated device (if any: access tokens are occasionally created without an associated
device ID), and the (now deactivated) access token.

If multiple modules implement this callback, Synapse runs them all in order.

## Example

The example module below implements authentication checkers for two different login types:
Expand Down
10 changes: 10 additions & 0 deletions docs/modules/presence_router_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ must return a dictionary that maps from Matrix user IDs (which can be local or r

Synapse will then attempt to send the specified presence updates to each user when possible.

If multiple modules implement this callback, Synapse merges all the dictionaries returned
by the callbacks. If multiple callbacks return a dictionary containing the same key,
Synapse concatenates the sets associated with this key from each dictionary.

### `get_interested_users`

```python
Expand All @@ -44,6 +48,12 @@ query. The returned users can be local or remote.
Alternatively the callback can return `synapse.module_api.PRESENCE_ALL_USERS`
to indicate that the user should receive updates from all known users.

If multiple modules implement this callback, they will be considered in order. Synapse
calls each callback one by one, and use a concatenation of all the `set`s returned by the
callbacks. If one callback returns `synapse.module_api.PRESENCE_ALL_USERS`, Synapse uses
this value instead. If this happens, Synapse does not call any of the subsequent
implementations of this callback.

## Example

The example below is a module that implements both presence router callbacks, and ensures
Expand Down
56 changes: 56 additions & 0 deletions docs/modules/spam_checker_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ either a `bool` to indicate whether the event must be rejected because of spam,
to indicate the event must be rejected because of spam and to give a rejection reason to
forward to clients.

If multiple modules implement this callback, they will be considered in order. If a
callback returns `False`, Synapse falls through to the next one. The value of the first
callback that does not return `False` will be used. If this happens, Synapse will not call
any of the subsequent implementations of this callback.

### `user_may_join_room`

```python
Expand All @@ -34,6 +39,11 @@ currently has a pending invite in the room.
This callback isn't called if the join is performed by a server administrator, or in the
context of a room creation.

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.

### `user_may_invite`

```python
Expand All @@ -44,6 +54,11 @@ Called when processing an invitation. The module must return a `bool` indicating
the inviter can invite the invitee to the given room. Both inviter and invitee are
represented by their Matrix user ID (e.g. `@alice:example.com`).

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.

### `user_may_send_3pid_invite`

```python
Expand Down Expand Up @@ -79,6 +94,11 @@ await user_may_send_3pid_invite(
**Note**: If the third-party identifier is already associated with a matrix user ID,
[`user_may_invite`](#user_may_invite) will be used instead.

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.

### `user_may_create_room`

```python
Expand All @@ -88,6 +108,11 @@ async def user_may_create_room(user: str) -> bool
Called when processing a room creation request. The module must return a `bool` indicating
whether the given user (represented by their Matrix user ID) is allowed to create a room.

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.

### `user_may_create_room_with_invites`

```python
Expand Down Expand Up @@ -117,6 +142,11 @@ corresponding list(s) will be empty.
since no invites are sent when cloning a room. To cover this case, modules also need to
implement `user_may_create_room`.

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.

### `user_may_create_room_alias`

```python
Expand All @@ -127,6 +157,11 @@ Called when trying to associate an alias with an existing room. The module must
`bool` indicating whether the given user (represented by their Matrix user ID) is allowed
to set the given alias.

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.

### `user_may_publish_room`

```python
Expand All @@ -137,6 +172,11 @@ Called when trying to publish a room to the homeserver's public rooms directory.
module must return a `bool` indicating whether the given user (represented by their
Matrix user ID) is allowed to publish the given room.

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_username_for_spam`

```python
Expand All @@ -154,6 +194,11 @@ is represented as a dictionary with the following keys:
The module is given a copy of the original dictionary, so modifying it from within the
module cannot modify a user's profile when included in user directory search results.

If multiple modules implement this callback, they will be considered in order. If a
callback returns `False`, Synapse falls through to the next one. The value of the first
callback that does not return `False` will be used. If this happens, Synapse will not call
any of the subsequent implementations of this callback.

### `check_registration_for_spam`

```python
Expand All @@ -179,6 +224,12 @@ The arguments passed to this callback are:
used during the registration process.
* `auth_provider_id`: The identifier of the SSO authentication provider, if any.

If multiple modules implement this callback, they will be considered in order. If a
callback returns `RegistrationBehaviour.ALLOW`, Synapse falls through to the next one.
The value of the first callback that does not return `RegistrationBehaviour.ALLOW` will
be used. If this happens, Synapse will not call any of the subsequent implementations of
this callback.

### `check_media_file_for_spam`

```python
Expand All @@ -191,6 +242,11 @@ async def check_media_file_for_spam(
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.

If multiple modules implement this callback, they will be considered in order. If a
callback returns `False`, Synapse falls through to the next one. The value of the first
callback that does not return `False` will be used. If this happens, Synapse will not call
any of the subsequent implementations of this callback.

## Example

The example below is a module that implements the spam checker callback
Expand Down
21 changes: 21 additions & 0 deletions docs/modules/third_party_rules_callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ 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.

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`

```python
Expand All @@ -63,6 +68,12 @@ 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`

```python
Expand All @@ -76,6 +87,11 @@ async def check_threepid_can_be_invited(
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`

```python
Expand All @@ -90,6 +106,11 @@ Called when changing the visibility of a room in the local public room directory
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.

## Example

The example below is a module that implements the third-party rules callback
Expand Down
15 changes: 15 additions & 0 deletions docs/modules/writing_a_module.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ configuration associated with the module in Synapse's configuration file.
See the documentation for the `ModuleApi` class
[here](https://github.com/matrix-org/synapse/blob/master/synapse/module_api/__init__.py).

## When Synapse runs with several modules configured

If Synapse is running with other modules configured, the order each module appears in
within the `modules` section of the Synapse configuration file might restrict what it can
or cannot register. See [this section](index.html#using-multiple-modules) for more
information.

On top of the rules listed in the link above, if a callback returns a value that should
cause the current operation to fail (e.g. if a callback checking an event returns with a
value that should cause the event to be denied), Synapse will fail the operation and
ignore any subsequent callbacks that should have been run after this one.

The documentation for each callback mentions how Synapse behaves when
multiple modules implement it.

## Handling the module's configuration

A module can implement the following static method:
Expand Down