From 3c7fd98b40695ecc7eefdaee1c53ebf1642e19c2 Mon Sep 17 00:00:00 2001
From: babolivier
The current spam checker interface is deprecated in favour of a new generic modules system.
+Authors of spam checker modules can refer to this documentation <https://matrix-org.github.io/synapse/develop/modules.html#porting-an-existing-module-that-uses-the-old-interface>
_
+to update their modules. Synapse administrators can refer to this documentation <https://matrix-org.github.io/synapse/develop/modules.html#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 August 2021.
+More module interfaces will be ported over to this new generic system in future versions +of Synapse.
room_invite_state_types
configuration settingThe room_invite_state_types
configuration setting has been deprecated and
@@ -2735,6 +2744,22 @@ a fresh config using Synapse by following the instructions in
#
# [1] https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html
+
+## Modules ##
+
+# Server admins can expand Synapse's functionality with external modules.
+#
+# See https://matrix-org.github.io/synapse/develop/modules.html for more
+# documentation on how to configure or create custom modules for Synapse.
+#
+modules:
+ # - module: my_super_module.MySuperClass
+ # config:
+ # do_thing: true
+ # - module: my_other_super_module.SomeClass
+ # config: {}
+
+
## Server ##
# The public-facing domain of the server
@@ -5195,19 +5220,6 @@ push:
#group_unread_count_by_room: false
-# Spam checkers are third-party modules that can block specific actions
-# of local users, such as creating rooms and registering undesirable
-# usernames, as well as remote users by redacting incoming events.
-#
-spam_checker:
- #- module: "my_custom_project.SuperSpamChecker"
- # config:
- # example_option: 'things'
- #- module: "some_other_project.BadEventStopper"
- # config:
- # example_stop_events_from: ['@bad:example.com']
-
-
## Rooms ##
# Controls whether locally-created rooms should be end-to-end encrypted by
@@ -7172,7 +7184,207 @@ space, it will start writing new data into where the purged data was.
VACUUM FULL;
(or
VACUUM;
for SQLite databases) on Synapse's database (see the related
PostgreSQL documentation).
-Synapse supports extending its functionality by configuring external modules.
+To use a module on Synapse, add it to the modules
section of the configuration file:
modules:
+ - module: my_super_module.MySuperClass
+ config:
+ do_thing: true
+ - module: my_other_super_module.SomeClass
+ config: {}
+
+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.
+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. Currently, only the +spam checker interface is compatible with this new system.
+A module is a Python class that uses Synapse's module API to interact with the +homeserver. It can register callbacks that Synapse will call on specific operations, as +well as web resources to attach to Synapse's web server.
+When instantiated, a module is given its parsed configuration as well as an instance of
+the synapse.module_api.ModuleApi
class. The configuration is a dictionary, and is
+either the output of the module's parse_config
static method (see below), or the
+configuration associated with the module in Synapse's configuration file.
See the documentation for the ModuleApi
class
+here.
A module can implement the following static method:
+@staticmethod
+def parse_config(config: dict) -> dict
+
+This method is given a dictionary resulting from parsing the YAML configuration for the
+module. It may modify it (for example by parsing durations expressed as strings (e.g.
+"5d") into milliseconds, etc.), and return the modified dictionary. It may also verify
+that the configuration is correct, and raise an instance of
+synapse.module_api.errors.ConfigError
if not.
Modules can register web resources onto Synapse's web server using the following module +API method:
+def ModuleApi.register_web_resource(path: str, resource: IResource)
+
+The path is the full absolute path to register the resource at. For example, if you
+register a resource for the path /_synapse/client/my_super_module/say_hello
, Synapse
+will serve it at http(s)://[HS_URL]/_synapse/client/my_super_module/say_hello
. Note
+that Synapse does not allow registering resources for several sub-paths in the /_matrix
+namespace (such as anything under /_matrix/client
for example). It is strongly
+recommended that modules register their web resources under the /_synapse/client
+namespace.
The provided resource is a Python class that implements Twisted's IResource +interface (such as Resource).
+Only one resource can be registered for a given path. If several modules attempt to +register a resource for the same path, the module that appears first in Synapse's +configuration file takes priority.
+Modules must register their web resources in their __init__
method.
Modules can use Synapse's module API to register callbacks. Callbacks are functions that +Synapse will call when performing specific actions. Callbacks must be asynchronous, and +are split in categories. A single module may implement callbacks from multiple categories, +and is under no obligation to implement all callbacks from the categories it registers +callbacks for.
+To register one of the callbacks described in this section, a module needs to use the
+module API's register_spam_checker_callbacks
method. The callback functions are passed
+to register_spam_checker_callbacks
as keyword arguments, with the callback name as the
+argument name and the function as its value. This is demonstrated in the example below.
The available spam checker callbacks are:
+def check_event_for_spam(event: "synapse.events.EventBase") -> Union[bool, str]
+
+Called when receiving an event from a client or via federation. The module can return
+either a bool
to indicate whether the event must be rejected because of spam, or a str
+to indicate the event must be rejected because of spam and to give a rejection reason to
+forward to clients.
def user_may_invite(inviter: str, invitee: str, room_id: str) -> bool
+
+Called when processing an invitation. The module must return a bool
indicating whether
+the inviter can invite the invitee to the given room. Both inviter and invitee are
+represented by their Matrix user ID (i.e. @alice:example.com
).
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.
def user_may_create_room_alias(user: str, room_alias: "synapse.types.RoomAlias") -> bool
+
+Called when trying to associate an alias with an existing room. The module must return a
+bool
indicating whether the given user (represented by their Matrix user ID) is allowed
+to set the given alias.
def user_may_publish_room(user: str, room_id: str) -> bool
+
+Called when trying to publish a room to the homeserver's public rooms directory. The
+module must return a bool
indicating whether the given user (represented by their
+Matrix user ID) is allowed to publish the given room.
def check_username_for_spam(user_profile: Dict[str, str]) -> bool
+
+Called when computing search results in the user directory. The module must return a
+bool
indicating whether the given user profile can appear in search results. The profile
+is represented as a dictionary with the following keys:
user_id
: The Matrix ID for this user.display_name
: The user's display name.avatar_url
: The mxc://
URL to the user's avatar.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.
+def check_registration_for_spam(
+ email_threepid: Optional[dict],
+ username: Optional[str],
+ request_info: Collection[Tuple[str, str]],
+ auth_provider_id: Optional[str] = None,
+) -> "synapse.spam_checker_api.RegistrationBehaviour"
+
+Called when registering a new user. The module must return a RegistrationBehaviour
+indicating whether the registration can go through or must be denied, or whether the user
+may be allowed to register but will be shadow banned.
The arguments passed to this callback are:
+email_threepid
: The email address used for registering, if any.username
: The username the user would like to register. Can be None
, meaning that
+Synapse will generate one later.request_info
: A collection of tuples, which first item is a user agent, and which
+second item is an IP address. These user agents and IP addresses are the ones that were
+used during the registration process.auth_provider_id
: The identifier of the SSO authentication provider, if any.def check_media_file_for_spam(
+ file_wrapper: "synapse.rest.media.v1.media_storage.ReadableFileWrapper",
+ 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.
+In order to port a module that uses Synapse's old module interface, its author needs to:
+register_[...]_callbacks
methods
+from the ModuleApi
class in the module's __init__
method (see this section
+for more info).Additionally, if the module is packaged with an additional web resource, the module
+should register this resource in its __init__
method using the register_web_resource
+method from the ModuleApi
class (see this section for
+more info).
The module's author should also update any example in the module's configuration to only
+use the new modules
section in Synapse's configuration file (see this section
+for more info).
The example below is a module that implements the spam checker callback
+user_may_create_room
to deny room creation to user @evilguy:example.com
, and registers
+a web resource to the path /_synapse/client/demo/hello
that returns a JSON object.
import json
+
+from twisted.web.resource import Resource
+from twisted.web.server import Request
+
+from synapse.module_api import ModuleApi
+
+
+class DemoResource(Resource):
+ def __init__(self, config):
+ super(DemoResource, self).__init__()
+ self.config = config
+
+ def render_GET(self, request: Request):
+ name = request.args.get(b"name")[0]
+ request.setHeader(b"Content-Type", b"application/json")
+ return json.dumps({"hello": name})
+
+
+class DemoModule:
+ def __init__(self, config: dict, api: ModuleApi):
+ self.config = config
+ self.api = api
+
+ self.api.register_web_resource(
+ path="/_synapse/client/demo/hello",
+ resource=DemoResource(self.config),
+ )
+
+ self.api.register_spam_checker_callbacks(
+ user_may_create_room=self.user_may_create_room,
+ )
+
+ @staticmethod
+ def parse_config(config):
+ return config
+
+ async def user_may_create_room(self, user: str) -> bool:
+ if user == "@evilguy:example.com":
+ return False
+
+ return True
+
+Note: this page of the Synapse documentation is now deprecated. For up to date +documentation on setting up or writing a spam checker module, please see +this page.
+Synapse has support to customize spam checking behavior. It can plug into a variety of events and affect how they are presented to users on your homeserver.
The spam checking behavior is implemented as a Python class, which must be @@ -7203,7 +7415,7 @@ call back into the homeserver internals.
Additionally, a parse_config
method is mandatory and receives the plugin config
dictionary. After parsing, It must return an object which will be
passed to __init__
later.
from synapse.spam_checker_api import RegistrationBehaviour
class ExampleSpamChecker:
@@ -7258,7 +7470,7 @@ custom logic, e.g. my_module.ExampleSpamChecker
.
config
is a dictionary that gets passed to the spam checker class.
-Example
+Example
This section might look like:
spam_checker:
- module: my_module.ExampleSpamChecker
@@ -7335,7 +7547,7 @@ user should receive presence information for all known users.
{"@bob:example.com", "@charlie:somewhere.org"}
is returned, this signifies that Alice
should receive presence updates sent by Bob and Charlie, regardless of whether these
users share a room.
-Example
+Example
Below is an example implementation of a presence router class.
from typing import Dict, Iterable, Set, Union
from synapse.events.presence_router import PresenceRouter
@@ -9307,7 +9519,7 @@ that were not kicked.
the old room to the new.
new_room_id
- A string representing the room ID of the new room.
-Example
+Example
Request:
POST /_synapse/admin/v1/shutdown_room/!somebadroom%3Aexample.com
@@ -11971,7 +12183,7 @@ connection errors.
received for each stream so that on reconneciton it can start streaming
from the correct place. Note: not all RDATA have valid tokens due to
batching. See RdataCommand
for more details.
-Example
+Example
An example iteraction is shown below. Each line is prefixed with '>'
or '<' to indicate which side is sending, these are not included on
the wire:
@@ -12216,7 +12428,7 @@ graph), and one where we remove redundant links (the transitive reduction of the
links graph) e.g. if we have chains C3 -> C2 -> C1
then the link C3 -> C1
would not be stored. Synapse uses the former implementations so that it doesn't
need to recurse to test reachability between chains.
-Example
+Example
An example auth graph would look like the following, where chains have been
formed based on type/state_key and are denoted by colour and are labelled with
(chain ID, sequence number)
. Links are denoted by the arrows (links in grey
--
cgit 1.5.1