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

Document Synapse's behaviour when multiple modules register the same
callback/web resource/etc.

Co-authored-by: reivilibre <oliverw@matrix.org>
This commit is contained in:
Brendan Abolivier 2021-10-18 18:26:52 +02:00 committed by GitHub
parent e8f24b6c35
commit 73743b8ad1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 154 additions and 8 deletions

1
changelog.d/11096.doc Normal file
View file

@ -0,0 +1 @@
Document Synapse's behaviour when dealing with multiple modules registering the same callbacks and/or handlers for the same HTTP endpoints.

View file

@ -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 `ORG_MATRIX_EXPIRED_ACCOUNT` and the HTTP status code 403. Note that this doesn't
invalidate the user's access token. 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` ### `on_user_registration`
```python ```python
@ -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 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 operations to keep track of them. (e.g. add them to a database table). The user is
represented by their Matrix user ID. represented by their Matrix user ID.
If multiple modules implement this callback, Synapse runs them all in order.

View file

@ -2,6 +2,11 @@
Synapse supports extending its functionality by configuring external modules. 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 ## Using modules
To use a module on Synapse, add it to the `modules` section of the configuration file: To use a module on Synapse, add it to the `modules` section of the configuration file:
@ -18,17 +23,31 @@ modules:
Each module is defined by a path to a Python class as well as a configuration. This 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. 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 ## Using multiple modules
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.
Also note that we are currently in the process of migrating module interfaces to this The order in which modules are listed in this section is important. When processing an
system. While some interfaces might be compatible with it, others still require action that can be handled by several modules, Synapse will always prioritise the module
configuring modules in another part of Synapse's configuration file. 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
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: Currently, only the following pre-existing interfaces are compatible with this new system:
* spam checker * spam checker
* third-party rules * third-party rules
* presence router * presence router
* password auth providers

View file

@ -44,6 +44,15 @@ instead.
If the authentication is unsuccessful, the module must return `None`. 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` ### `check_3pid_auth`
```python ```python
@ -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. `@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 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` ### `on_logged_out`
@ -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 deactivated device (if any: access tokens are occasionally created without an associated
device ID), and the (now deactivated) access token. device ID), and the (now deactivated) access token.
If multiple modules implement this callback, Synapse runs them all in order.
## Example ## Example
The example module below implements authentication checkers for two different login types: The example module below implements authentication checkers for two different login types:

View file

@ -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. 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` ### `get_interested_users`
```python ```python
@ -44,6 +48,12 @@ query. The returned users can be local or remote.
Alternatively the callback can return `synapse.module_api.PRESENCE_ALL_USERS` Alternatively the callback can return `synapse.module_api.PRESENCE_ALL_USERS`
to indicate that the user should receive updates from all known 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 ## Example
The example below is a module that implements both presence router callbacks, and ensures The example below is a module that implements both presence router callbacks, and ensures

View file

@ -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 to indicate the event must be rejected because of spam and to give a rejection reason to
forward to clients. 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` ### `user_may_join_room`
```python ```python
@ -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 This callback isn't called if the join is performed by a server administrator, or in the
context of a room creation. 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` ### `user_may_invite`
```python ```python
@ -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 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`). 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` ### `user_may_send_3pid_invite`
```python ```python
@ -79,6 +94,11 @@ await user_may_send_3pid_invite(
**Note**: If the third-party identifier is already associated with a matrix user ID, **Note**: If the third-party identifier is already associated with a matrix user ID,
[`user_may_invite`](#user_may_invite) will be used instead. [`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` ### `user_may_create_room`
```python ```python
@ -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 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. 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` ### `user_may_create_room_with_invites`
```python ```python
@ -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 since no invites are sent when cloning a room. To cover this case, modules also need to
implement `user_may_create_room`. 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` ### `user_may_create_room_alias`
```python ```python
@ -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 `bool` indicating whether the given user (represented by their Matrix user ID) is allowed
to set the given alias. 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` ### `user_may_publish_room`
```python ```python
@ -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 module must return a `bool` indicating whether the given user (represented by their
Matrix user ID) is allowed to publish the given room. 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` ### `check_username_for_spam`
```python ```python
@ -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 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. 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` ### `check_registration_for_spam`
```python ```python
@ -179,6 +224,12 @@ The arguments passed to this callback are:
used during the registration process. used during the registration process.
* `auth_provider_id`: The identifier of the SSO authentication provider, if any. * `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` ### `check_media_file_for_spam`
```python ```python
@ -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 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. 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 ## Example
The example below is a module that implements the spam checker callback The example below is a module that implements the spam checker callback

View file

@ -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 Note that replacing the event only works for events sent by local users, not for events
received over federation. 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` ### `on_create_room`
```python ```python
@ -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`), 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`. 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` ### `check_threepid_can_be_invited`
```python ```python
@ -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). 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. 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` ### `check_visibility_can_be_modified`
```python ```python
@ -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 visibility is a string that's either "public" or "private". The module must return a
boolean indicating whether the change can go through. 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 ## Example
The example below is a module that implements the third-party rules callback The example below is a module that implements the third-party rules callback

View file

@ -12,6 +12,21 @@ configuration associated with the module in Synapse's configuration file.
See the documentation for the `ModuleApi` class See the documentation for the `ModuleApi` class
[here](https://github.com/matrix-org/synapse/blob/master/synapse/module_api/__init__.py). [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 ## Handling the module's configuration
A module can implement the following static method: A module can implement the following static method: