This commit is contained in:
babolivier committed 2021-09-08 17:15:19 +00:00
1 parent e0a526daef
commit e13feb3880
76 files changed
+2448 -468

No files matched your search

+188 -396
View File
@@ -101,7 +101,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><li class="part-title">Introduction</li><li class="chapter-item expanded "><a href="welcome_and_overview.html">Welcome and Overview</a></li><li class="chapter-item expanded affix "><li class="part-title">Setup</li><li class="chapter-item expanded "><a href="setup/installation.html">Installation</a></li><li class="chapter-item expanded "><a href="postgres.html">Using Postgres</a></li><li class="chapter-item expanded "><a href="reverse_proxy.html">Configuring a Reverse Proxy</a></li><li class="chapter-item expanded "><a href="setup/forward_proxy.html">Configuring a Forward/Outbound Proxy</a></li><li class="chapter-item expanded "><a href="turn-howto.html">Configuring a Turn Server</a></li><li class="chapter-item expanded "><a href="delegate.html">Delegation</a></li><li class="chapter-item expanded affix "><li class="part-title">Upgrading</li><li class="chapter-item expanded "><a href="upgrade.html">Upgrading between Synapse Versions</a></li><li class="chapter-item expanded "><a href="MSC1711_certificates_FAQ.html">Upgrading from pre-Synapse 1.0</a></li><li class="chapter-item expanded affix "><li class="part-title">Usage</li><li class="chapter-item expanded "><a href="federate.html">Federation</a></li><li class="chapter-item expanded "><a href="usage/configuration/index.html">Configuration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/configuration/homeserver_sample_config.html">Homeserver Sample Config File</a></li><li class="chapter-item expanded "><a href="usage/configuration/logging_sample_config.html">Logging Sample Config File</a></li><li class="chapter-item expanded "><a href="structured_logging.html">Structured Logging</a></li><li class="chapter-item expanded "><a href="templates.html">Templates</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/index.html">User Authentication</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Single-Sign On</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="openid.html">OpenID Connect</a></li><li class="chapter-item expanded "><div>SAML</div></li><li class="chapter-item expanded "><div>CAS</div></li><li class="chapter-item expanded "><a href="sso_mapping_providers.html">SSO Mapping Providers</a></li></ol></li><li class="chapter-item expanded "><a href="password_auth_providers.html">Password Auth Providers</a></li><li class="chapter-item expanded "><a href="jwt.html">JSON Web Tokens</a></li></ol></li><li class="chapter-item expanded "><a href="CAPTCHA_SETUP.html">Registration Captcha</a></li><li class="chapter-item expanded "><a href="application_services.html">Application Services</a></li><li class="chapter-item expanded "><a href="server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="consent_tracking.html">Consent Tracking</a></li><li class="chapter-item expanded "><a href="development/url_previews.html">URL Previews</a></li><li class="chapter-item expanded "><a href="user_directory.html">User Directory</a></li><li class="chapter-item expanded "><a href="message_retention_policies.html">Message Retention Policies</a></li><li class="chapter-item expanded "><a href="modules.html">Pluggable Modules</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Third Party Rules</div></li><li class="chapter-item expanded "><a href="spam_checker.html">Spam Checker</a></li><li class="chapter-item expanded "><a href="presence_router_module.html">Presence Router</a></li><li class="chapter-item expanded "><div>Media Storage Providers</div></li></ol></li><li class="chapter-item expanded "><a href="workers.html">Workers</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="synctl_workers.html">Using synctl with Workers</a></li><li class="chapter-item expanded "><a href="systemd-with-workers/index.html">Systemd</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="usage/administration/index.html">Administration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/administration/admin_api/index.html">Admin API</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="admin_api/account_validity.html">Account Validity</a></li><li class="chapter-item expanded "><a href="admin_api/delete_group.html">Delete Group</a></li><li class="chapter-item expanded "><a href="admin_api/event_reports.html">Event Reports</a></li><li class="chapter-item expanded "><a href="admin_api/media_admin_api.html">Media</a></li><li class="chapter-item expanded "><a href="admin_api/purge_history_api.html">Purge History</a></li><li class="chapter-item expanded "><a href="admin_api/register_api.html">Register Users</a></li><li class="chapter-item expanded "><a href="usage/administration/admin_api/registration_tokens.html">Registration Tokens</a></li><li class="chapter-item expanded ">Line truncated
<ol class="chapter"><li class="chapter-item expanded affix "><li class="part-title">Introduction</li><li class="chapter-item expanded "><a href="welcome_and_overview.html">Welcome and Overview</a></li><li class="chapter-item expanded affix "><li class="part-title">Setup</li><li class="chapter-item expanded "><a href="setup/installation.html">Installation</a></li><li class="chapter-item expanded "><a href="postgres.html">Using Postgres</a></li><li class="chapter-item expanded "><a href="reverse_proxy.html">Configuring a Reverse Proxy</a></li><li class="chapter-item expanded "><a href="setup/forward_proxy.html">Configuring a Forward/Outbound Proxy</a></li><li class="chapter-item expanded "><a href="turn-howto.html">Configuring a Turn Server</a></li><li class="chapter-item expanded "><a href="delegate.html">Delegation</a></li><li class="chapter-item expanded affix "><li class="part-title">Upgrading</li><li class="chapter-item expanded "><a href="upgrade.html">Upgrading between Synapse Versions</a></li><li class="chapter-item expanded "><a href="MSC1711_certificates_FAQ.html">Upgrading from pre-Synapse 1.0</a></li><li class="chapter-item expanded affix "><li class="part-title">Usage</li><li class="chapter-item expanded "><a href="federate.html">Federation</a></li><li class="chapter-item expanded "><a href="usage/configuration/index.html">Configuration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/configuration/homeserver_sample_config.html">Homeserver Sample Config File</a></li><li class="chapter-item expanded "><a href="usage/configuration/logging_sample_config.html">Logging Sample Config File</a></li><li class="chapter-item expanded "><a href="structured_logging.html">Structured Logging</a></li><li class="chapter-item expanded "><a href="templates.html">Templates</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/index.html">User Authentication</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Single-Sign On</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="openid.html">OpenID Connect</a></li><li class="chapter-item expanded "><div>SAML</div></li><li class="chapter-item expanded "><div>CAS</div></li><li class="chapter-item expanded "><a href="sso_mapping_providers.html">SSO Mapping Providers</a></li></ol></li><li class="chapter-item expanded "><a href="password_auth_providers.html">Password Auth Providers</a></li><li class="chapter-item expanded "><a href="jwt.html">JSON Web Tokens</a></li></ol></li><li class="chapter-item expanded "><a href="CAPTCHA_SETUP.html">Registration Captcha</a></li><li class="chapter-item expanded "><a href="application_services.html">Application Services</a></li><li class="chapter-item expanded "><a href="server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="consent_tracking.html">Consent Tracking</a></li><li class="chapter-item expanded "><a href="development/url_previews.html">URL Previews</a></li><li class="chapter-item expanded "><a href="user_directory.html">User Directory</a></li><li class="chapter-item expanded "><a href="message_retention_policies.html">Message Retention Policies</a></li><li class="chapter-item expanded "><a href="modules/index.html">Pluggable Modules</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="modules/writing_a_module.html">Writing a module</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="modules/spam_checker_callbacks.html">Spam checker callbacks</a></li><li class="chapter-item expanded "><a href="modules/third_party_rules_callbacks.html">Third-party rules callbacks</a></li><li class="chapter-item expanded "><a href="modules/presence_router_callbacks.html">Presence router callbacks</a></li><li class="chapter-item expanded "><a href="modules/account_validity_callbacks.html">Account validity callbacks</a></li><li class="chapter-item expanded "><a href="modules/porting_legacy_module.html">Porting a legacy module to the new interface</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="workers.html">Workers</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="synctl_workers.html">Using synctl with Workers</a></li><li class="chapter-item expanded "><a href="systemd-with-workers/index.html">Systemd</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="usage/administration/index.html">Administration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/administration/admin_api/index.html">Admin API</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="admin_api/account_validity.html">Account Validity</a></li><li class="chapter-item expanded "><a href="admin_api/delete_group.html">Delete Group</a></li><li class="chapter-item expanded "><a href="admin_api/event_reports.html">Event Reports</a></li><li class="chapter-item expaLine truncated
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
@@ -7518,9 +7518,14 @@ provenance of the modules they use on their homeserver and make sure the modules
running malicious code on their instance.</p>
<p>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.</p>
<h2 id="writing-a-module"><a class="header" href="#writing-a-module">Writing a module</a></h2>
configuring modules in another part of Synapse's configuration file.</p>
<p>Currently, only the following pre-existing interfaces are compatible with this new system:</p>
<ul>
<li>spam checker</li>
<li>third-party rules</li>
<li>presence router</li>
</ul>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="writing-a-module"><a class="header" href="#writing-a-module">Writing a module</a></h1>
<p>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.</p>
@@ -7530,7 +7535,7 @@ either the output of the module's <code>parse_config</code> static method (see b
configuration associated with the module in Synapse's configuration file.</p>
<p>See the documentation for the <code>ModuleApi</code> class
<a href="https://github.com/matrix-org/synapse/blob/master/synapse/module_api/__init__.py">here</a>.</p>
<h3 id="handling-the-modules-configuration"><a class="header" href="#handling-the-modules-configuration">Handling the module's configuration</a></h3>
<h2 id="handling-the-modules-configuration"><a class="header" href="#handling-the-modules-configuration">Handling the module's configuration</a></h2>
<p>A module can implement the following static method:</p>
<pre><code class="language-python">@staticmethod
def parse_config(config: dict) -&gt; dict
@@ -7540,7 +7545,7 @@ module. It may modify it (for example by parsing durations expressed as strings
&quot;5d&quot;) into milliseconds, etc.), and return the modified dictionary. It may also verify
that the configuration is correct, and raise an instance of
<code>synapse.module_api.errors.ConfigError</code> if not.</p>
<h3 id="registering-a-web-resource"><a class="header" href="#registering-a-web-resource">Registering a web resource</a></h3>
<h2 id="registering-a-web-resource"><a class="header" href="#registering-a-web-resource">Registering a web resource</a></h2>
<p>Modules can register web resources onto Synapse's web server using the following module
API method:</p>
<pre><code class="language-python">def ModuleApi.register_web_resource(path: str, resource: IResource) -&gt; None
@@ -7558,7 +7563,7 @@ interface (such as <a href="https://twistedmatrix.com/documents/current/api/twis
register a resource for the same path, the module that appears first in Synapse's
configuration file takes priority.</p>
<p>Modules <strong>must</strong> register their web resources in their <code>__init__</code> method.</p>
<h3 id="registering-a-callback"><a class="header" href="#registering-a-callback">Registering a callback</a></h3>
<h2 id="registering-a-callback"><a class="header" href="#registering-a-callback">Registering a callback</a></h2>
<p>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,
@@ -7567,38 +7572,46 @@ callbacks for.</p>
<p>Modules can register callbacks using one of the module API's <code>register_[...]_callbacks</code>
methods. The callback functions are passed to these methods as keyword arguments, with
the callback name as the argument name and the function as its value. This is demonstrated
in the example below. A <code>register_[...]_callbacks</code> method exists for each module type
documented in this section.</p>
<h4 id="spam-checker-callbacks"><a class="header" href="#spam-checker-callbacks">Spam checker callbacks</a></h4>
in the example below. A <code>register_[...]_callbacks</code> method exists for each category.</p>
<p>Callbacks for each category can be found on their respective page of the
<a href="https://matrix-org.github.io/synapse">Synapse documentation website</a>.</p>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="spam-checker-callbacks"><a class="header" href="#spam-checker-callbacks">Spam checker callbacks</a></h1>
<p>Spam checker callbacks allow module developers to implement spam mitigation actions for
Synapse instances. Spam checker callbacks can be registered using the module API's
<code>register_spam_checker_callbacks</code> method.</p>
<h2 id="callbacks"><a class="header" href="#callbacks">Callbacks</a></h2>
<p>The available spam checker callbacks are:</p>
<h3 id="check_event_for_spam"><a class="header" href="#check_event_for_spam"><code>check_event_for_spam</code></a></h3>
<pre><code class="language-python">async def check_event_for_spam(event: &quot;synapse.events.EventBase&quot;) -&gt; Union[bool, str]
</code></pre>
<p>Called when receiving an event from a client or via federation. The module can return
either a <code>bool</code> to indicate whether the event must be rejected because of spam, or a <code>str</code>
to indicate the event must be rejected because of spam and to give a rejection reason to
forward to clients.</p>
<h3 id="user_may_invite"><a class="header" href="#user_may_invite"><code>user_may_invite</code></a></h3>
<pre><code class="language-python">async def user_may_invite(inviter: str, invitee: str, room_id: str) -&gt; bool
</code></pre>
<p>Called when processing an invitation. The module must return a <code>bool</code> indicating whether
the inviter can invite the invitee to the given room. Both inviter and invitee are
represented by their Matrix user ID (e.g. <code>@alice:example.com</code>).</p>
<h3 id="user_may_create_room"><a class="header" href="#user_may_create_room"><code>user_may_create_room</code></a></h3>
<pre><code class="language-python">async def user_may_create_room(user: str) -&gt; bool
</code></pre>
<p>Called when processing a room creation request. The module must return a <code>bool</code> indicating
whether the given user (represented by their Matrix user ID) is allowed to create a room.</p>
<h3 id="user_may_create_room_alias"><a class="header" href="#user_may_create_room_alias"><code>user_may_create_room_alias</code></a></h3>
<pre><code class="language-python">async def user_may_create_room_alias(user: str, room_alias: &quot;synapse.types.RoomAlias&quot;) -&gt; bool
</code></pre>
<p>Called when trying to associate an alias with an existing room. The module must return a
<code>bool</code> indicating whether the given user (represented by their Matrix user ID) is allowed
to set the given alias.</p>
<h3 id="user_may_publish_room"><a class="header" href="#user_may_publish_room"><code>user_may_publish_room</code></a></h3>
<pre><code class="language-python">async def user_may_publish_room(user: str, room_id: str) -&gt; bool
</code></pre>
<p>Called when trying to publish a room to the homeserver's public rooms directory. The
module must return a <code>bool</code> indicating whether the given user (represented by their
Matrix user ID) is allowed to publish the given room.</p>
<h3 id="check_username_for_spam"><a class="header" href="#check_username_for_spam"><code>check_username_for_spam</code></a></h3>
<pre><code class="language-python">async def check_username_for_spam(user_profile: Dict[str, str]) -&gt; bool
</code></pre>
<p>Called when computing search results in the user directory. The module must return a
@@ -7611,6 +7624,7 @@ is represented as a dictionary with the following keys:</p>
</ul>
<p>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.</p>
<h3 id="check_registration_for_spam"><a class="header" href="#check_registration_for_spam"><code>check_registration_for_spam</code></a></h3>
<pre><code class="language-python">async def check_registration_for_spam(
email_threepid: Optional[dict],
username: Optional[str],
@@ -7631,6 +7645,7 @@ second item is an IP address. These user agents and IP addresses are the ones th
used during the registration process.</li>
<li><code>auth_provider_id</code>: The identifier of the SSO authentication provider, if any.</li>
</ul>
<h3 id="check_media_file_for_spam"><a class="header" href="#check_media_file_for_spam"><code>check_media_file_for_spam</code></a></h3>
<pre><code class="language-python">async def check_media_file_for_spam(
file_wrapper: &quot;synapse.rest.media.v1.media_storage.ReadableFileWrapper&quot;,
file_info: &quot;synapse.rest.media.v1._base.FileInfo&quot;,
@@ -7638,31 +7653,56 @@ used during the registration process.</li>
</code></pre>
<p>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.</p>
<h4 id="account-validity-callbacks"><a class="header" href="#account-validity-callbacks">Account validity callbacks</a></h4>
<p>Account validity callbacks allow module developers to add extra steps to verify the
validity on an account, i.e. see if a user can be granted access to their account on the
Synapse instance. Account validity callbacks can be registered using the module API's
<code>register_account_validity_callbacks</code> method.</p>
<p>The available account validity callbacks are:</p>
<pre><code class="language-python">async def is_user_expired(user: str) -&gt; Optional[bool]
<h2 id="example"><a class="header" href="#example">Example</a></h2>
<p>The example below is a module that implements the spam checker callback
<code>check_event_for_spam</code> to deny any message sent by users whose Matrix user IDs are
mentioned in a configured list, and registers a web resource to the path
<code>/_synapse/client/list_spam_checker/is_evil</code> that returns a JSON object indicating
whether the provided user appears in that list.</p>
<pre><code class="language-python">import json
from typing import Union
from twisted.web.resource import Resource
from twisted.web.server import Request
from synapse.module_api import ModuleApi
class IsUserEvilResource(Resource):
def __init__(self, config):
super(IsUserEvilResource, self).__init__()
self.evil_users = config.get(&quot;evil_users&quot;) or []
def render_GET(self, request: Request):
user = request.args.get(b&quot;user&quot;)[0]
request.setHeader(b&quot;Content-Type&quot;, b&quot;application/json&quot;)
return json.dumps({&quot;evil&quot;: user in self.evil_users})
class ListSpamChecker:
def __init__(self, config: dict, api: ModuleApi):
self.api = api
self.evil_users = config.get(&quot;evil_users&quot;) or []
self.api.register_spam_checker_callbacks(
check_event_for_spam=self.check_event_for_spam,
)
self.api.register_web_resource(
path=&quot;/_synapse/client/list_spam_checker/is_evil&quot;,
resource=IsUserEvilResource(config),
)
async def check_event_for_spam(self, event: &quot;synapse.events.EventBase&quot;) -&gt; Union[bool, str]:
return event.sender not in self.evil_users
</code></pre>
<p>Called when processing any authenticated request (except for logout requests). The module
can return a <code>bool</code> to indicate whether the user has expired and should be locked out of
their account, or <code>None</code> if the module wasn't able to figure it out. The user is
represented by their Matrix user ID (e.g. <code>@alice:example.com</code>).</p>
<p>If the module returns <code>True</code>, the current request will be denied with the error code
<code>ORG_MATRIX_EXPIRED_ACCOUNT</code> and the HTTP status code 403. Note that this doesn't
invalidate the user's access token.</p>
<pre><code class="language-python">async def on_user_registration(user: str) -&gt; None
</code></pre>
<p>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.</p>
<h4 id="third-party-rules-callbacks"><a class="header" href="#third-party-rules-callbacks">Third party rules callbacks</a></h4>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="third-party-rules-callbacks"><a class="header" href="#third-party-rules-callbacks">Third party rules callbacks</a></h1>
<p>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 <code>register_third_party_rules_callbacks</code> method.</p>
<h2 id="callbacks-1"><a class="header" href="#callbacks-1">Callbacks</a></h2>
<p>The available third party rules callbacks are:</p>
<h3 id="check_event_allowed"><a class="header" href="#check_event_allowed"><code>check_event_allowed</code></a></h3>
<pre><code class="language-python">async def check_event_allowed(
event: &quot;synapse.events.EventBase&quot;,
state_events: &quot;synapse.types.StateMap&quot;,
@@ -7690,6 +7730,7 @@ that, it is recommended the module calls <code>event.get_dict()</code> to get th
dictionary, and modify the returned dictionary accordingly.</p>
<p>Note that replacing the event only works for events sent by local users, not for events
received over federation.</p>
<h3 id="on_create_room"><a class="header" href="#on_create_room"><code>on_create_room</code></a></h3>
<pre><code class="language-python">async def on_create_room(
requester: &quot;synapse.types.Requester&quot;,
request_content: dict,
@@ -7703,15 +7744,65 @@ for a list of possible parameters), and a boolean indicating whether the user pe
the request is a server admin.</p>
<p>Modules can modify the <code>request_content</code> (by e.g. adding events to its <code>initial_state</code>),
or deny the room's creation by raising a <code>module_api.errors.SynapseError</code>.</p>
<h4 id="presence-router-callbacks"><a class="header" href="#presence-router-callbacks">Presence router callbacks</a></h4>
<h3 id="check_threepid_can_be_invited"><a class="header" href="#check_threepid_can_be_invited"><code>check_threepid_can_be_invited</code></a></h3>
<pre><code class="language-python">async def check_threepid_can_be_invited(
medium: str,
address: str,
state_events: &quot;synapse.types.StateMap&quot;,
) -&gt; bool:
</code></pre>
<p>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.</p>
<h3 id="check_visibility_can_be_modified"><a class="header" href="#check_visibility_can_be_modified"><code>check_visibility_can_be_modified</code></a></h3>
<pre><code class="language-python">async def check_visibility_can_be_modified(
room_id: str,
state_events: &quot;synapse.types.StateMap&quot;,
new_visibility: str,
) -&gt; bool:
</code></pre>
<p>Called when changing the visibility of a room in the local public room directory. The
visibility is a string that's either &quot;public&quot; or &quot;private&quot;. The module must return a
boolean indicating whether the change can go through.</p>
<h2 id="example-1"><a class="header" href="#example-1">Example</a></h2>
<p>The example below is a module that implements the third-party rules callback
<code>check_event_allowed</code> to censor incoming messages as dictated by a third-party service.</p>
<pre><code class="language-python">from typing import Optional, Tuple
from synapse.module_api import ModuleApi
_DEFAULT_CENSOR_ENDPOINT = &quot;https://my-internal-service.local/censor-event&quot;
class EventCensorer:
def __init__(self, config: dict, api: ModuleApi):
self.api = api
self._endpoint = config.get(&quot;endpoint&quot;, _DEFAULT_CENSOR_ENDPOINT)
self.api.register_third_party_rules_callbacks(
check_event_allowed=self.check_event_allowed,
)
async def check_event_allowed(
self,
event: &quot;synapse.events.EventBase&quot;,
state_events: &quot;synapse.types.StateMap&quot;,
) -&gt; 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[&quot;content&quot;] = new_event_content
return event_dict
</code></pre>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="presence-router-callbacks"><a class="header" href="#presence-router-callbacks">Presence router callbacks</a></h1>
<p>Presence router callbacks allow module developers to specify additional users (local or remote)
to receive certain presence updates from local users. Presence router callbacks can be
registered using the module API's <code>register_presence_router_callbacks</code> method.</p>
<h2 id="callbacks-2"><a class="header" href="#callbacks-2">Callbacks</a></h2>
<p>The available presence router callbacks are:</p>
<h3 id="get_users_for_states"><a class="header" href="#get_users_for_states"><code>get_users_for_states</code></a></h3>
<pre><code class="language-python">async def get_users_for_states(
self,
state_updates: Iterable[&quot;synapse.api.UserPresenceState&quot;],
) -&gt; Dict[str, Set[&quot;synapse.api.UserPresenceState&quot;]]:
) -&gt; Dict[str, Set[&quot;synapse.api.UserPresenceState&quot;]]
</code></pre>
<p><strong>Requires</strong> <code>get_interested_users</code> to also be registered</p>
<p>Called when processing updates to the presence state of one or more users. This callback can
@@ -7719,9 +7810,9 @@ be used to instruct the server to forward that presence state to specific users.
must return a dictionary that maps from Matrix user IDs (which can be local or remote) to the
<code>UserPresenceState</code> changes that they should be forwarded.</p>
<p>Synapse will then attempt to send the specified presence updates to each user when possible.</p>
<h3 id="get_interested_users"><a class="header" href="#get_interested_users"><code>get_interested_users</code></a></h3>
<pre><code class="language-python">async def get_interested_users(
self,
user_id: str
user_id: str
) -&gt; Union[Set[str], &quot;synapse.module_api.PRESENCE_ALL_USERS&quot;]
</code></pre>
<p><strong>Requires</strong> <code>get_users_for_states</code> to also be registered</p>
@@ -7733,383 +7824,84 @@ should return the Matrix user IDs of the users whose presence state they are all
query. The returned users can be local or remote. </p>
<p>Alternatively the callback can return <code>synapse.module_api.PRESENCE_ALL_USERS</code>
to indicate that the user should receive updates from all known users.</p>
<p>For example, if the user <code>@alice:example.org</code> is passed to this method, and the Set
<code>{&quot;@bob:example.com&quot;, &quot;@charlie:somewhere.org&quot;}</code> is returned, this signifies that Alice
should receive presence updates sent by Bob and Charlie, regardless of whether these users
share a room.</p>
<h3 id="porting-an-existing-module-that-uses-the-old-interface"><a class="header" href="#porting-an-existing-module-that-uses-the-old-interface">Porting an existing module that uses the old interface</a></h3>
<p>In order to port a module that uses Synapse's old module interface, its author needs to:</p>
<ul>
<li>ensure the module's callbacks are all asynchronous.</li>
<li>register their callbacks using one or more of the <code>register_[...]_callbacks</code> methods
from the <code>ModuleApi</code> class in the module's <code>__init__</code> method (see <a href="modules.html#registering-a-callback">this section</a>
for more info).</li>
</ul>
<p>Additionally, if the module is packaged with an additional web resource, the module
should register this resource in its <code>__init__</code> method using the <code>register_web_resource</code>
method from the <code>ModuleApi</code> class (see <a href="modules.html#registering-a-web-resource">this section</a> for
more info).</p>
<p>The module's author should also update any example in the module's configuration to only
use the new <code>modules</code> section in Synapse's configuration file (see <a href="modules.html#using-modules">this section</a>
for more info).</p>
<h3 id="example"><a class="header" href="#example">Example</a></h3>
<p>The example below is a module that implements the spam checker callback
<code>user_may_create_room</code> to deny room creation to user <code>@evilguy:example.com</code>, and registers
a web resource to the path <code>/_synapse/client/demo/hello</code> that returns a JSON object.</p>
<pre><code class="language-python">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&quot;name&quot;)[0]
request.setHeader(b&quot;Content-Type&quot;, b&quot;application/json&quot;)
return json.dumps({&quot;hello&quot;: name})
class DemoModule:
def __init__(self, config: dict, api: ModuleApi):
self.config = config
self.api = api
self.api.register_web_resource(
path=&quot;/_synapse/client/demo/hello&quot;,
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) -&gt; bool:
if user == &quot;@evilguy:example.com&quot;:
return False
return True
</code></pre>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h2 style="color:red">
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
<a href="modules.html">this page</a>.
</h2>
<h1 id="handling-spam-in-synapse"><a class="header" href="#handling-spam-in-synapse">Handling spam in Synapse</a></h1>
<p>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.</p>
<p>The spam checking behavior is implemented as a Python class, which must be
able to be imported by the running Synapse.</p>
<h2 id="python-spam-checker-class"><a class="header" href="#python-spam-checker-class">Python spam checker class</a></h2>
<p>The Python class is instantiated with two objects:</p>
<ul>
<li>Any configuration (see below).</li>
<li>An instance of <code>synapse.module_api.ModuleApi</code>.</li>
</ul>
<p>It then implements methods which return a boolean to alter behavior in Synapse.
All the methods must be defined.</p>
<p>There's a generic method for checking every event (<code>check_event_for_spam</code>), as
well as some specific methods:</p>
<ul>
<li><code>user_may_invite</code></li>
<li><code>user_may_create_room</code></li>
<li><code>user_may_create_room_alias</code></li>
<li><code>user_may_publish_room</code></li>
<li><code>check_username_for_spam</code></li>
<li><code>check_registration_for_spam</code></li>
<li><code>check_media_file_for_spam</code></li>
</ul>
<p>The details of each of these methods (as well as their inputs and outputs)
are documented in the <code>synapse.events.spamcheck.SpamChecker</code> class.</p>
<p>The <code>ModuleApi</code> class provides a way for the custom spam checker class to
call back into the homeserver internals.</p>
<p>Additionally, a <code>parse_config</code> method is mandatory and receives the plugin config
dictionary. After parsing, It must return an object which will be
passed to <code>__init__</code> later.</p>
<h3 id="example-1"><a class="header" href="#example-1">Example</a></h3>
<pre><code class="language-python">from synapse.spam_checker_api import RegistrationBehaviour
class ExampleSpamChecker:
def __init__(self, config, api):
self.config = config
self.api = api
@staticmethod
def parse_config(config):
return config
async def check_event_for_spam(self, foo):
return False # allow all events
async def user_may_invite(self, inviter_userid, invitee_userid, room_id):
return True # allow all invites
async def user_may_create_room(self, userid):
return True # allow all room creations
async def user_may_create_room_alias(self, userid, room_alias):
return True # allow all room aliases
async def user_may_publish_room(self, userid, room_id):
return True # allow publishing of all rooms
async def check_username_for_spam(self, user_profile):
return False # allow all usernames
async def check_registration_for_spam(
self,
email_threepid,
username,
request_info,
auth_provider_id,
):
return RegistrationBehaviour.ALLOW # allow all registrations
async def check_media_file_for_spam(self, file_wrapper, file_info):
return False # allow all media
</code></pre>
<h2 id="configuration-2"><a class="header" href="#configuration-2">Configuration</a></h2>
<p>Modify the <code>spam_checker</code> section of your <code>homeserver.yaml</code> in the following
manner:</p>
<p>Create a list entry with the keys <code>module</code> and <code>config</code>.</p>
<ul>
<li>
<p><code>module</code> should point to the fully qualified Python class that implements your
custom logic, e.g. <code>my_module.ExampleSpamChecker</code>.</p>
</li>
<li>
<p><code>config</code> is a dictionary that gets passed to the spam checker class.</p>
</li>
</ul>
<h3 id="example-2"><a class="header" href="#example-2">Example</a></h3>
<p>This section might look like:</p>
<pre><code class="language-yaml">spam_checker:
- module: my_module.ExampleSpamChecker
config:
# Enable or disable a specific option in ExampleSpamChecker.
my_custom_option: true
</code></pre>
<p>More spam checkers can be added in tandem by appending more items to the list. An
action is blocked when at least one of the configured spam checkers flags it.</p>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
<p>The <a href="https://github.com/matrix-org/mjolnir">Mjolnir</a> project is a full fledged
example using the Synapse spam checking API, including a bot for dynamic
configuration.</p>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h2 style="color:red">
This page of the Synapse documentation is now deprecated. For up to date
documentation on setting up or writing a presence router module, please see
<a href="modules.html">this page</a>.
</h2>
<h1 id="presence-router-module"><a class="header" href="#presence-router-module">Presence Router Module</a></h1>
<p>Synapse supports configuring a module that can specify additional users
(local or remote) to should receive certain presence updates from local
users.</p>
<p>Note that routing presence via Application Service transactions is not
currently supported.</p>
<p>The presence routing module is implemented as a Python class, which will
be imported by the running Synapse.</p>
<h2 id="python-presence-router-class"><a class="header" href="#python-presence-router-class">Python Presence Router Class</a></h2>
<p>The Python class is instantiated with two objects:</p>
<ul>
<li>A configuration object of some type (see below).</li>
<li>An instance of <code>synapse.module_api.ModuleApi</code>.</li>
</ul>
<p>It then implements methods related to presence routing.</p>
<p>Note that one method of <code>ModuleApi</code> that may be useful is:</p>
<pre><code class="language-python">async def ModuleApi.send_local_online_presence_to(users: Iterable[str]) -&gt; None
</code></pre>
<p>which can be given a list of local or remote MXIDs to broadcast known, online user
presence to (for those users that the receiving user is considered interested in).
It does not include state for users who are currently offline, and it can only be
called on workers that support sending federation. Additionally, this method must
only be called from the process that has been configured to write to the
the <a href="workers.html#stream-writers">presence stream</a>.
By default, this is the main process, but another worker can be configured to do
so.</p>
<h3 id="module-structure"><a class="header" href="#module-structure">Module structure</a></h3>
<p>Below is a list of possible methods that can be implemented, and whether they are
required.</p>
<h4 id="parse_config"><a class="header" href="#parse_config"><code>parse_config</code></a></h4>
<pre><code class="language-python">def parse_config(config_dict: dict) -&gt; Any
</code></pre>
<p><strong>Required.</strong> A static method that is passed a dictionary of config options, and
should return a validated config object. This method is described further in
<a href="presence_router_module.html#configuration">Configuration</a>.</p>
<h4 id="get_users_for_states"><a class="header" href="#get_users_for_states"><code>get_users_for_states</code></a></h4>
<pre><code class="language-python">async def get_users_for_states(
self,
state_updates: Iterable[UserPresenceState],
) -&gt; Dict[str, Set[UserPresenceState]]:
</code></pre>
<p><strong>Required.</strong> An asynchronous method that is passed an iterable of user presence
state. This method can determine whether a given presence update should be sent to certain
users. It does this by returning a dictionary with keys representing local or remote
Matrix User IDs, and values being a python set
of <code>synapse.handlers.presence.UserPresenceState</code> instances.</p>
<p>Synapse will then attempt to send the specified presence updates to each user when
possible.</p>
<h4 id="get_interested_users"><a class="header" href="#get_interested_users"><code>get_interested_users</code></a></h4>
<pre><code class="language-python">async def get_interested_users(self, user_id: str) -&gt; Union[Set[str], str]
</code></pre>
<p><strong>Required.</strong> An asynchronous method that is passed a single Matrix User ID. This
method is expected to return the users that the passed in user may be interested in the
presence of. Returned users may be local or remote. The presence routed as a result of
what this method returns is sent in addition to the updates already sent between users
that share a room together. Presence updates are deduplicated.</p>
<p>This method should return a python set of Matrix User IDs, or the object
<code>synapse.events.presence_router.PresenceRouter.ALL_USERS</code> to indicate that the passed
user should receive presence information for <em>all</em> known users.</p>
<p>For clarity, if the user <code>@alice:example.org</code> is passed to this method, and the Set
<code>{&quot;@bob:example.com&quot;, &quot;@charlie:somewhere.org&quot;}</code> is returned, this signifies that Alice
should receive presence updates sent by Bob and Charlie, regardless of whether these
users share a room.</p>
<h3 id="example-3"><a class="header" href="#example-3">Example</a></h3>
<p>Below is an example implementation of a presence router class.</p>
<h2 id="example-2"><a class="header" href="#example-2">Example</a></h2>
<p>The example below is a module that implements both presence router callbacks, and ensures
that <code>@alice:example.org</code> receives all presence updates from <code>@bob:example.com</code> and
<code>@charlie:somewhere.org</code>, regardless of whether Alice shares a room with any of them.</p>
<pre><code class="language-python">from typing import Dict, Iterable, Set, Union
from synapse.events.presence_router import PresenceRouter
from synapse.handlers.presence import UserPresenceState
from synapse.module_api import ModuleApi
class PresenceRouterConfig:
def __init__(self):
# Config options with their defaults
# A list of users to always send all user presence updates to
self.always_send_to_users = [] # type: List[str]
# A list of users to ignore presence updates for. Does not affect
# shared-room presence relationships
self.blacklisted_users = [] # type: List[str]
class ExamplePresenceRouter:
&quot;&quot;&quot;An example implementation of synapse.presence_router.PresenceRouter.
Supports routing all presence to a configured set of users, or a subset
of presence from certain users to members of certain rooms.
class CustomPresenceRouter:
def __init__(self, config: dict, api: ModuleApi):
self.api = api
Args:
config: A configuration object.
module_api: An instance of Synapse's ModuleApi.
&quot;&quot;&quot;
def __init__(self, config: PresenceRouterConfig, module_api: ModuleApi):
self._config = config
self._module_api = module_api
@staticmethod
def parse_config(config_dict: dict) -&gt; PresenceRouterConfig:
&quot;&quot;&quot;Parse a configuration dictionary from the homeserver config, do
some validation and return a typed PresenceRouterConfig.
Args:
config_dict: The configuration dictionary.
Returns:
A validated config object.
&quot;&quot;&quot;
# Initialise a typed config object
config = PresenceRouterConfig()
always_send_to_users = config_dict.get(&quot;always_send_to_users&quot;)
blacklisted_users = config_dict.get(&quot;blacklisted_users&quot;)
# Do some validation of config options... otherwise raise a
# synapse.config.ConfigError.
config.always_send_to_users = always_send_to_users
config.blacklisted_users = blacklisted_users
return config
self.api.register_presence_router_callbacks(
get_users_for_states=self.get_users_for_states,
get_interested_users=self.get_interested_users,
)
async def get_users_for_states(
self,
state_updates: Iterable[UserPresenceState],
) -&gt; Dict[str, Set[UserPresenceState]]:
&quot;&quot;&quot;Given an iterable of user presence updates, determine where each one
needs to go. Returned results will not affect presence updates that are
sent between users who share a room.
Args:
state_updates: An iterable of user presence state updates.
Returns:
A dictionary of user_id -&gt; set of UserPresenceState that the user should
receive.
&quot;&quot;&quot;
destination_users = {} # type: Dict[str, Set[UserPresenceState]
# Ignore any updates for blacklisted users
desired_updates = set()
state_updates: Iterable[&quot;synapse.api.UserPresenceState&quot;],
) -&gt; Dict[str, Set[&quot;synapse.api.UserPresenceState&quot;]]:
res = {}
for update in state_updates:
if update.state_key not in self._config.blacklisted_users:
desired_updates.add(update)
if (
update.user_id == &quot;@bob:example.com&quot;
or update.user_id == &quot;@charlie:somewhere.org&quot;
):
res.setdefault(&quot;@alice:example.com&quot;, set()).add(update)
# Send all presence updates to specific users
for user_id in self._config.always_send_to_users:
destination_users[user_id] = desired_updates
return destination_users
return res
async def get_interested_users(
self,
user_id: str,
) -&gt; Union[Set[str], PresenceRouter.ALL_USERS]:
&quot;&quot;&quot;
Retrieve a list of users that `user_id` is interested in receiving the
presence of. This will be in addition to those they share a room with.
Optionally, the object PresenceRouter.ALL_USERS can be returned to indicate
that this user should receive all incoming local and remote presence updates.
Note that this method will only be called for local users.
Args:
user_id: A user requesting presence updates.
Returns:
A set of user IDs to return additional presence updates for, or
PresenceRouter.ALL_USERS to return presence updates for all other users.
&quot;&quot;&quot;
if user_id in self._config.always_send_to_users:
return PresenceRouter.ALL_USERS
) -&gt; Union[Set[str], &quot;synapse.module_api.PRESENCE_ALL_USERS&quot;]:
if user_id == &quot;@alice:example.com&quot;:
return {&quot;@bob:example.com&quot;, &quot;@charlie:somewhere.org&quot;}
return set()
</code></pre>
<h4 id="a-note-on-get_users_for_states-and-get_interested_users"><a class="header" href="#a-note-on-get_users_for_states-and-get_interested_users">A note on <code>get_users_for_states</code> and <code>get_interested_users</code></a></h4>
<p>Both of these methods are effectively two different sides of the same coin. The logic
regarding which users should receive updates for other users should be the same
between them.</p>
<p><code>get_users_for_states</code> is called when presence updates come in from either federation
or local users, and is used to either direct local presence to remote users, or to
wake up the sync streams of local users to collect remote presence.</p>
<p>In contrast, <code>get_interested_users</code> is used to determine the users that presence should
be fetched for when a local user is syncing. This presence is then retrieved, before
being fed through <code>get_users_for_states</code> once again, with only the syncing user's
routing information pulled from the resulting dictionary.</p>
<p>Their routing logic should thus line up, else you may run into unintended behaviour.</p>
<h2 id="configuration-3"><a class="header" href="#configuration-3">Configuration</a></h2>
<p>Once you've crafted your module and installed it into the same Python environment as
Synapse, amend your homeserver config file with the following.</p>
<pre><code class="language-yaml">presence:
enabled: true
presence_router:
module: my_module.ExamplePresenceRouter
config:
# Any configuration options for your module. The below is an example.
# of setting options for ExamplePresenceRouter.
always_send_to_users: [&quot;@presence_gobbler:example.org&quot;]
blacklisted_users:
- &quot;@alice:example.com&quot;
- &quot;@bob:example.com&quot;
...
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="account-validity-callbacks"><a class="header" href="#account-validity-callbacks">Account validity callbacks</a></h1>
<p>Account validity callbacks allow module developers to add extra steps to verify the
validity on an account, i.e. see if a user can be granted access to their account on the
Synapse instance. Account validity callbacks can be registered using the module API's
<code>register_account_validity_callbacks</code> method.</p>
<p>The available account validity callbacks are:</p>
<h3 id="is_user_expired"><a class="header" href="#is_user_expired"><code>is_user_expired</code></a></h3>
<pre><code class="language-python">async def is_user_expired(user: str) -&gt; Optional[bool]
</code></pre>
<p>The contents of <code>config</code> will be passed as a Python dictionary to the static
<code>parse_config</code> method of your class. The object returned by this method will
then be passed to the <code>__init__</code> method of your module as <code>config</code>.</p>
<p>Called when processing any authenticated request (except for logout requests). The module
can return a <code>bool</code> to indicate whether the user has expired and should be locked out of
their account, or <code>None</code> if the module wasn't able to figure it out. The user is
represented by their Matrix user ID (e.g. <code>@alice:example.com</code>).</p>
<p>If the module returns <code>True</code>, the current request will be denied with the error code
<code>ORG_MATRIX_EXPIRED_ACCOUNT</code> and the HTTP status code 403. Note that this doesn't
invalidate the user's access token.</p>
<h3 id="on_user_registration"><a class="header" href="#on_user_registration"><code>on_user_registration</code></a></h3>
<pre><code class="language-python">async def on_user_registration(user: str) -&gt; None
</code></pre>
<p>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.</p>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="porting-an-existing-module-that-uses-the-old-interface"><a class="header" href="#porting-an-existing-module-that-uses-the-old-interface">Porting an existing module that uses the old interface</a></h1>
<p>In order to port a module that uses Synapse's old module interface, its author needs to:</p>
<ul>
<li>ensure the module's callbacks are all asynchronous.</li>
<li>register their callbacks using one or more of the <code>register_[...]_callbacks</code> methods
from the <code>ModuleApi</code> class in the module's <code>__init__</code> method (see <a href="modules/writing_a_module.html#registering-a-callback">this section</a>
for more info).</li>
</ul>
<p>Additionally, if the module is packaged with an additional web resource, the module
should register this resource in its <code>__init__</code> method using the <code>register_web_resource</code>
method from the <code>ModuleApi</code> class (see <a href="modules/writing_a_module.html#registering-a-web-resource">this section</a> for
more info).</p>
<p>The module's author should also update any example in the module's configuration to only
use the new <code>modules</code> section in Synapse's configuration file (see <a href="modules/index.html#using-modules">this section</a>
for more info).</p>
<div id="chapter_begin" style="break-before: page; page-break-before: always;"></div><h1 id="scaling-synapse-via-workers"><a class="header" href="#scaling-synapse-via-workers">Scaling synapse via workers</a></h1>
<p>For small instances it recommended to run Synapse in the default monolith mode.
For larger instances where performance is a concern it can be helpful to split
@@ -12731,7 +12523,7 @@ connection errors.</p>
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 <code>RdataCommand</code> for more details.</p>
<h3 id="example-4"><a class="header" href="#example-4">Example</a></h3>
<h3 id="example-3"><a class="header" href="#example-3">Example</a></h3>
<p>An example iteraction is shown below. Each line is prefixed with '&gt;'
or '&lt;' to indicate which side is sending, these are <em>not</em> included on
the wire:</p>
@@ -13027,7 +12819,7 @@ graph), and one where we remove redundant links (the transitive reduction of the
links graph) e.g. if we have chains <code>C3 -&gt; C2 -&gt; C1</code> then the link <code>C3 -&gt; C1</code>
would not be stored. Synapse uses the former implementations so that it doesn't
need to recurse to test reachability between chains.</p>
<h3 id="example-5"><a class="header" href="#example-5">Example</a></h3>
<h3 id="example-4"><a class="header" href="#example-4">Example</a></h3>
<p>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
<code>(chain ID, sequence number)</code>. Links are denoted by the arrows (links in grey