summary refs log tree commit diff
path: root/develop/modules
diff options
context:
space:
mode:
authorbabolivier <babolivier@users.noreply.github.com>2021-10-18 16:27:18 +0000
committerbabolivier <babolivier@users.noreply.github.com>2021-10-18 16:27:18 +0000
commit22e6e75a6ec31382d0d51221ce52502126872a00 (patch)
tree559c9aec38c7f5683706fc53eea1f542a49cbb49 /develop/modules
parentdeploy: 55731333488bfd53ece117938dde1cef710eef68 (diff)
downloadsynapse-22e6e75a6ec31382d0d51221ce52502126872a00.tar.xz
deploy: 73743b8ad194c6e833432110b7d0cd1ba2ad1e6a
Diffstat (limited to 'develop/modules')
-rw-r--r--develop/modules/account_validity_callbacks.html5
-rw-r--r--develop/modules/index.html30
-rw-r--r--develop/modules/password_auth_provider_callbacks.html15
-rw-r--r--develop/modules/presence_router_callbacks.html8
-rw-r--r--develop/modules/spam_checker_callbacks.html45
-rw-r--r--develop/modules/third_party_rules_callbacks.html17
-rw-r--r--develop/modules/writing_a_module.html11
7 files changed, 123 insertions, 8 deletions
diff --git a/develop/modules/account_validity_callbacks.html b/develop/modules/account_validity_callbacks.html
index e5065164c3..1d6d0a734d 100644
--- a/develop/modules/account_validity_callbacks.html
+++ b/develop/modules/account_validity_callbacks.html
@@ -198,12 +198,17 @@ 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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>None</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>None</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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>
+<p>If multiple modules implement this callback, Synapse runs them all in order.</p>
 
                     </main>
 
diff --git a/develop/modules/index.html b/develop/modules/index.html
index 956e84dab8..a512784333 100644
--- a/develop/modules/index.html
+++ b/develop/modules/index.html
@@ -184,6 +184,10 @@
 
                         <h1 id="modules"><a class="header" href="#modules">Modules</a></h1>
 <p>Synapse supports extending its functionality by configuring external modules.</p>
+<p><strong>Note</strong>: 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.</p>
 <h2 id="using-modules"><a class="header" href="#using-modules">Using modules</a></h2>
 <p>To use a module on Synapse, add it to the <code>modules</code> section of the configuration file:</p>
 <pre><code class="language-yaml">modules:
@@ -195,18 +199,30 @@
 </code></pre>
 <p>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.</p>
-<p><strong>Note</strong>: 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.</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.</p>
+<h2 id="using-multiple-modules"><a class="header" href="#using-multiple-modules">Using multiple modules</a></h2>
+<p>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:</p>
+<ul>
+<li>If several modules register the same callback, the callback registered by the module
+that appears first is used.</li>
+<li>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.</li>
+</ul>
+<p>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.</p>
+<h2 id="current-status"><a class="header" href="#current-status">Current status</a></h2>
+<p>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.</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>
+<li>password auth providers</li>
 </ul>
 
                     </main>
diff --git a/develop/modules/password_auth_provider_callbacks.html b/develop/modules/password_auth_provider_callbacks.html
index d0762dc5a9..dbc38dfd12 100644
--- a/develop/modules/password_auth_provider_callbacks.html
+++ b/develop/modules/password_auth_provider_callbacks.html
@@ -215,6 +215,13 @@ the client.</p>
 <code>/login</code> request. If the module doesn't wish to return a callback, it must return <code>None</code>
 instead.</p>
 <p>If the authentication is unsuccessful, the module must return <code>None</code>.</p>
+<p>If multiple modules register an auth checker for the same login type but with different
+fields, Synapse will refuse to start.</p>
+<p>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 <code>None</code>, then the
+authentication fails.</p>
 <h3 id="check_3pid_auth"><a class="header" href="#check_3pid_auth"><code>check_3pid_auth</code></a></h3>
 <pre><code class="language-python">async def check_3pid_auth(
     medium: str, 
@@ -233,7 +240,12 @@ and the user's password.</p>
 <p>If the authentication is successful, the module must return the user's Matrix ID (e.g. 
 <code>@alice:example.com</code>) and optionally a callback to be called with the response to the <code>/login</code> request.
 If the module doesn't wish to return a callback, it must return None instead.</p>
-<p>If the authentication is unsuccessful, the module must return None.</p>
+<p>If the authentication is unsuccessful, the module must return <code>None</code>.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>None</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>None</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback. If every callback return <code>None</code>,
+the authentication is denied.</p>
 <h3 id="on_logged_out"><a class="header" href="#on_logged_out"><code>on_logged_out</code></a></h3>
 <pre><code class="language-python">async def on_logged_out(
     user_id: str,
@@ -244,6 +256,7 @@ If the module doesn't wish to return a callback, it must return None instead.</p
 <p>Called during a logout request for a user. It is passed the qualified user ID, the ID of the
 deactivated device (if any: access tokens are occasionally created without an associated
 device ID), and the (now deactivated) access token.</p>
+<p>If multiple modules implement this callback, Synapse runs them all in order.</p>
 <h2 id="example"><a class="header" href="#example">Example</a></h2>
 <p>The example module below implements authentication checkers for two different login types: </p>
 <ul>
diff --git a/develop/modules/presence_router_callbacks.html b/develop/modules/presence_router_callbacks.html
index d325ca4c6a..ff95a5936f 100644
--- a/develop/modules/presence_router_callbacks.html
+++ b/develop/modules/presence_router_callbacks.html
@@ -199,6 +199,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>
+<p>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. </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(
     user_id: str
@@ -213,6 +216,11 @@ 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>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 <code>set</code>s returned by the
+callbacks. If one callback returns <code>synapse.module_api.PRESENCE_ALL_USERS</code>, Synapse uses
+this value instead. If this happens, Synapse does not call any of the subsequent
+implementations of this callback.</p>
 <h2 id="example"><a class="header" href="#example">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
diff --git a/develop/modules/spam_checker_callbacks.html b/develop/modules/spam_checker_callbacks.html
index 604cbd9076..f43444d402 100644
--- a/develop/modules/spam_checker_callbacks.html
+++ b/develop/modules/spam_checker_callbacks.html
@@ -195,6 +195,10 @@ Synapse instances. Spam checker callbacks can be registered using the module API
 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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>False</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>False</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
 <h3 id="user_may_join_room"><a class="header" href="#user_may_join_room"><code>user_may_join_room</code></a></h3>
 <pre><code class="language-python">async def user_may_join_room(user: str, room: str, is_invited: bool) -&gt; bool
 </code></pre>
@@ -205,12 +209,20 @@ whether the user can join the room. The user is represented by their Matrix user
 currently has a pending invite in the room.</p>
 <p>This callback isn't called if the join is performed by a server administrator, or in the
 context of a room creation.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
 <h3 id="user_may_send_3pid_invite"><a class="header" href="#user_may_send_3pid_invite"><code>user_may_send_3pid_invite</code></a></h3>
 <pre><code class="language-python">async def user_may_send_3pid_invite(
     inviter: str,
@@ -237,11 +249,19 @@ for more information regarding third-party identifiers.</p>
 </code></pre>
 <p><strong>Note</strong>: If the third-party identifier is already associated with a matrix user ID,
 <a href="#user_may_invite"><code>user_may_invite</code></a> will be used instead.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
 <h3 id="user_may_create_room_with_invites"><a class="header" href="#user_may_create_room_with_invites"><code>user_may_create_room_with_invites</code></a></h3>
 <pre><code class="language-python">async def user_may_create_room_with_invites(
     user: str,
@@ -263,18 +283,30 @@ corresponding list(s) will be empty.</p>
 <p><strong>Note</strong>: This callback is not called when a room is cloned (e.g. during a room upgrade)
 since no invites are sent when cloning a room. To cover this case, modules also need to
 implement <code>user_may_create_room</code>.</p>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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>
@@ -288,6 +320,10 @@ 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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>False</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>False</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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],
@@ -309,6 +345,11 @@ 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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>RegistrationBehaviour.ALLOW</code>, Synapse falls through to the next one.
+The value of the first callback that does not return <code>RegistrationBehaviour.ALLOW</code> will
+be used. If this happens, Synapse will not call any of the subsequent implementations of
+this callback.</p>
 <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;,
@@ -317,6 +358,10 @@ 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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>False</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>False</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
 <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
diff --git a/develop/modules/third_party_rules_callbacks.html b/develop/modules/third_party_rules_callbacks.html
index 6087e5a870..adf9d398f6 100644
--- a/develop/modules/third_party_rules_callbacks.html
+++ b/develop/modules/third_party_rules_callbacks.html
@@ -216,6 +216,10 @@ 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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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;,
@@ -230,6 +234,11 @@ 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>
+<p>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.</p>
 <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,
@@ -239,6 +248,10 @@ or deny the room's creation by raising a <code>module_api.errors.SynapseError</c
 </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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</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,
@@ -249,6 +262,10 @@ The module must return a boolean indicating whether the invite can go through.</
 <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>
+<p>If multiple modules implement this callback, they will be considered in order. If a
+callback returns <code>True</code>, Synapse falls through to the next one. The value of the first
+callback that does not return <code>True</code> will be used. If this happens, Synapse will not call
+any of the subsequent implementations of this callback.</p>
 <h2 id="example"><a class="header" href="#example">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>
diff --git a/develop/modules/writing_a_module.html b/develop/modules/writing_a_module.html
index 240e171761..c91d9ec9b3 100644
--- a/develop/modules/writing_a_module.html
+++ b/develop/modules/writing_a_module.html
@@ -192,6 +192,17 @@ 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>
+<h2 id="when-synapse-runs-with-several-modules-configured"><a class="header" href="#when-synapse-runs-with-several-modules-configured">When Synapse runs with several modules configured</a></h2>
+<p>If Synapse is running with other modules configured, the order each module appears in
+within the <code>modules</code> section of the Synapse configuration file might restrict what it can
+or cannot register. See <a href="index.html#using-multiple-modules">this section</a> for more
+information.</p>
+<p>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.</p>
+<p>The documentation for each callback mentions how Synapse behaves when
+multiple modules implement it.</p>
 <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