From 7a927aac59bad94111ef585dc9770bfc92280e6d Mon Sep 17 00:00:00 2001
From: DMRobertson The available worker applications documentation
+has been updated to reflect that calls to the The following admin APIs were deprecated in Synapse 1.25
@@ -3274,6 +3280,24 @@ listeners:
# bind_addresses: ['::1', '127.0.0.1']
# type: manhole
+# Connection settings for the manhole
+#
+manhole_settings:
+ # The username for the manhole. This defaults to 'matrix'.
+ #
+ #username: manhole
+
+ # The password for the manhole. This defaults to 'rabbithole'.
+ #
+ #password: mypassword
+
+ # The private and public SSH key pair used to encrypt the manhole traffic.
+ # If these are left unset, then hardcoded and non-secret keys are used,
+ # which could allow traffic to be intercepted if sent over a public network.
+ #
+ #ssh_priv_key_path: CONFDIR/id_rsa
+ #ssh_pub_key_path: CONFDIR/id_rsa.pub
+
# Forward extremities can build up in a room due to networking delays between
# homeservers. Once this happens in a large room, calculation of the state of
# that room can become quite expensive. To mitigate this, once the number of
@@ -4014,6 +4038,27 @@ url_preview_accept_language:
# - en
+# oEmbed allows for easier embedding content from a website. It can be
+# used for generating URLs previews of services which support it.
+#
+oembed:
+ # A default list of oEmbed providers is included with Synapse.
+ #
+ # Uncomment the following to disable using these default oEmbed URLs.
+ # Defaults to 'false'.
+ #
+ #disable_default_providers: true
+
+ # Additional files with oEmbed configuration (each should be in the
+ # form of providers.json).
+ #
+ # By default, this list is empty (so only the default providers.json
+ # is used).
+ #
+ #additional_providers:
+ # - oembed/my_providers.json
+
+
## Captcha ##
# See docs/CAPTCHA_SETUP.md for full details of configuring this.
@@ -4986,7 +5031,7 @@ password_config:
#
#require_lowercase: true
- # Whether a password must contain at least one lowercase letter.
+ # Whether a password must contain at least one uppercase letter.
# Defaults to 'false'.
#
#require_uppercase: true
@@ -7225,29 +7270,10 @@ consent uri for that user.Upgrading to v1.43.0
+The spaces summary APIs can now be handled by workers
+/spaces
, /hierarchy
, and
+/summary
endpoints can now be routed to workers for both client API and
+federation requests.Upgrading to v1.42.0
Removal of old Room Admin API
consent_uri
in the error.)
Design notes on a URL previewing service for Matrix:
-Options are:
-The GET /_matrix/media/r0/preview_url
endpoint provides a generic preview API
+for URLs which outputs Open Graph responses (with some Matrix
+specific additions).
This does have trade-offs compared to other designs:
When Synapse is asked to preview a URL it does the following:
+url_preview_url_blacklist
in the
+config).Best solution is probably a combination of both 2 and 4.
-This is a bit magical though in that the preview could come from two entirely different sources - the sending HS or your local one. However, this can always be exposed to users: "Generate your own URL previews if none are available?"
-This is tantamount also to senders calculating their own thumbnails for sending in advance of the main content - we are trusting the sender not to lie about the content in the thumbnail. Whereas currently thumbnails are calculated by the receiving homeserver to avoid this attack.
-However, this kind of phishing attack does exist whether we let senders pick their thumbnails or not, in that a malicious sender can send normal text messages around the attachment claiming it to be legitimate. We could rely on (future) reputation/abuse management to punish users who phish (be it with bogus metadata or bogus descriptions). Bogus metadata is particularly bad though, especially if it's avoidable.
-As a first cut, let's do #2 and have the receiver hit the API to calculate its own previews (as it does currently for image thumbnails). We can then extend/optimise this to option 4 as a special extra if needed.
-GET /_matrix/media/r0/preview_url?url=http://wherever.com
-200 OK
-{
- "og:type" : "article"
- "og:url" : "https://twitter.com/matrixdotorg/status/684074366691356672"
- "og:title" : "Matrix on Twitter"
- "og:image" : "https://pbs.twimg.com/profile_images/500400952029888512/yI0qtFi7_400x400.png"
- "og:description" : "“Synapse 0.12 is out! Lots of polishing, performance & bugfixes: /sync API, /r0 prefix, fulltext search, 3PID invites https://t.co/5alhXLLEGP”"
- "og:site_name" : "Twitter"
-}
-
-The in-memory cache expires after 1 hour.
+Expired entries in the database cache (and their associated media files) are +deleted every 10 seconds. The default expiration time is 1 hour from download.
The user directory is currently maintained based on the 'visible' users on this particular server - i.e. ones which your account shares a room with, or @@ -7343,6 +7343,52 @@ DB corruption) get stale or out of sync. If this happens, for now the solution to fix it is to execute the SQL here and then restart synapse. This should then start a background task to flush the current tables and regenerate the directory.
+There are five relevant tables that collectively form the "user directory". +Three of them track a master list of all the users we could search for. +The last two (collectively called the "search tables") track who can +see who.
+From all of these tables we exclude three types of local user:
+user_directory
. This contains the user_id, display name and avatar we'll
+return when you search the directory.
user_directory_search
. To be joined to user_directory
. It contains an extra
+column that enables full text search based on user ids and display names.
+Different schemas for SQLite and Postgres with different code paths to match.
user_directory_stream_pos
. When the initial background update to populate
+the directory is complete, we record a stream position here. This indicates
+that synapse should now listen for room changes and incrementally update
+the directory where necessary.
users_in_public_rooms
. Contains associations between users and the public rooms they're in.
+Used to determine which users are in public rooms and should be publicly visible in the directory.
users_who_share_private_rooms
. Rows are triples (L, M, room id)
where L
+is a local user and M
is a local or remote user. L
and M
should be
+different, but this isn't enforced by a constraint.
Synapse admins can enable support for message retention policies on their homeserver. Message retention policies exist at a room level, @@ -7518,9 +7564,14 @@ provenance of the modules they use on their homeserver and make sure the modules 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.
-Currently, only the following pre-existing interfaces are 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.
@@ -7530,7 +7581,7 @@ either the output of the module'sparse_config
static method (see b
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
@@ -7540,7 +7591,7 @@ module. It may modify it (for example by parsing durations expressed as strings
"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.
-Registering a web resource
+Registering a web resource
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) -> None
@@ -7558,7 +7609,7 @@ interface (such as Registering a callback
+Registering a callback
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 +7618,46 @@ callbacks for.
Modules can register callbacks using one of the module API's register_[...]_callbacks
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 register_[...]_callbacks
method exists for each module type
-documented in this section.
-Spam checker callbacks
+in the example below. A register_[...]_callbacks
method exists for each category.
+Callbacks for each category can be found on their respective page of the
+Synapse documentation website.
+Spam checker callbacks
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
register_spam_checker_callbacks
method.
+Callbacks
The available spam checker callbacks are:
+check_event_for_spam
async 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.
+user_may_invite
async 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 (e.g. @alice:example.com
).
+user_may_create_room
async def user_may_create_room(user: str) -> bool
Called when processing a room creation request. The module must return a bool
indicating
whether the given user (represented by their Matrix user ID) is allowed to create a room.
+user_may_create_room_alias
async 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.
+user_may_publish_room
async 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.
+check_username_for_spam
async 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
@@ -7611,6 +7670,7 @@ is represented as a dictionary with the following keys:
The module is given a copy of the original dictionary, so modifying it from within the
module cannot modify a user's profile when included in user directory search results.
+check_registration_for_spam
async def check_registration_for_spam(
email_threepid: Optional[dict],
username: Optional[str],
@@ -7631,6 +7691,7 @@ second item is an IP address. These user agents and IP addresses are the ones th
used during the registration process.
auth_provider_id
: The identifier of the SSO authentication provider, if any.check_media_file_for_spam
async def check_media_file_for_spam(
file_wrapper: "synapse.rest.media.v1.media_storage.ReadableFileWrapper",
file_info: "synapse.rest.media.v1._base.FileInfo",
@@ -7638,31 +7699,56 @@ used during the registration process.
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.
-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
-register_account_validity_callbacks
method.
The available account validity callbacks are:
-async def is_user_expired(user: str) -> Optional[bool]
-
-Called when processing any authenticated request (except for logout requests). The module
-can return a bool
to indicate whether the user has expired and should be locked out of
-their account, or None
if the module wasn't able to figure it out. The user is
-represented by their Matrix user ID (e.g. @alice:example.com
).
If the module returns True
, the current request will be denied with the error code
-ORG_MATRIX_EXPIRED_ACCOUNT
and the HTTP status code 403. Note that this doesn't
-invalidate the user's access token.
async def on_user_registration(user: str) -> None
+Example
+The example below is a module that implements the spam checker callback
+check_event_for_spam
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
+/_synapse/client/list_spam_checker/is_evil
that returns a JSON object indicating
+whether the provided user appears in that list.
+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("evil_users") or []
+
+ def render_GET(self, request: Request):
+ user = request.args.get(b"user")[0]
+ request.setHeader(b"Content-Type", b"application/json")
+ return json.dumps({"evil": user in self.evil_users})
+
+
+class ListSpamChecker:
+ def __init__(self, config: dict, api: ModuleApi):
+ self.api = api
+ self.evil_users = config.get("evil_users") or []
+
+ self.api.register_spam_checker_callbacks(
+ check_event_for_spam=self.check_event_for_spam,
+ )
+
+ self.api.register_web_resource(
+ path="/_synapse/client/list_spam_checker/is_evil",
+ resource=IsUserEvilResource(config),
+ )
+
+ async def check_event_for_spam(self, event: "synapse.events.EventBase") -> Union[bool, str]:
+ return event.sender not in self.evil_users
-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.
-Third party rules callbacks
+Third party rules callbacks
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 register_third_party_rules_callbacks
method.
+Callbacks
The available third party rules callbacks are:
+check_event_allowed
async def check_event_allowed(
event: "synapse.events.EventBase",
state_events: "synapse.types.StateMap",
@@ -7690,6 +7776,7 @@ that, it is recommended the module calls event.get_dict()
to get th
dictionary, and modify the returned dictionary accordingly.
Note that replacing the event only works for events sent by local users, not for events
received over federation.
+on_create_room
async def on_create_room(
requester: "synapse.types.Requester",
request_content: dict,
@@ -7703,15 +7790,65 @@ for a list of possible parameters), and a boolean indicating whether the user pe
the request is a server admin.
Modules can modify the request_content
(by e.g. adding events to its initial_state
),
or deny the room's creation by raising a module_api.errors.SynapseError
.
-Presence router callbacks
+check_threepid_can_be_invited
+async def check_threepid_can_be_invited(
+ medium: str,
+ address: str,
+ state_events: "synapse.types.StateMap",
+) -> bool:
+
+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.
+check_visibility_can_be_modified
+async def check_visibility_can_be_modified(
+ room_id: str,
+ state_events: "synapse.types.StateMap",
+ new_visibility: str,
+) -> bool:
+
+Called when changing the visibility of a room in the local public room directory. The
+visibility is a string that's either "public" or "private". The module must return a
+boolean indicating whether the change can go through.
+Example
+The example below is a module that implements the third-party rules callback
+check_event_allowed
to censor incoming messages as dictated by a third-party service.
+from typing import Optional, Tuple
+
+from synapse.module_api import ModuleApi
+
+_DEFAULT_CENSOR_ENDPOINT = "https://my-internal-service.local/censor-event"
+
+class EventCensorer:
+ def __init__(self, config: dict, api: ModuleApi):
+ self.api = api
+ self._endpoint = config.get("endpoint", _DEFAULT_CENSOR_ENDPOINT)
+
+ self.api.register_third_party_rules_callbacks(
+ check_event_allowed=self.check_event_allowed,
+ )
+
+ async def check_event_allowed(
+ self,
+ event: "synapse.events.EventBase",
+ state_events: "synapse.types.StateMap",
+ ) -> 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["content"] = new_event_content
+ return event_dict
+
+Presence router callbacks
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 register_presence_router_callbacks
method.
+Callbacks
The available presence router callbacks are:
+get_users_for_states
async def get_users_for_states(
- self,
state_updates: Iterable["synapse.api.UserPresenceState"],
-) -> Dict[str, Set["synapse.api.UserPresenceState"]]:
+) -> Dict[str, Set["synapse.api.UserPresenceState"]]
Requires get_interested_users
to also be registered
Called when processing updates to the presence state of one or more users. This callback can
@@ -7719,9 +7856,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
UserPresenceState
changes that they should be forwarded.
Synapse will then attempt to send the specified presence updates to each user when possible.
+get_interested_users
async def get_interested_users(
- self,
- user_id: str
+ user_id: str
) -> Union[Set[str], "synapse.module_api.PRESENCE_ALL_USERS"]
Requires get_users_for_states
to also be registered
@@ -7733,383 +7870,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.
Alternatively the callback can return synapse.module_api.PRESENCE_ALL_USERS
to indicate that the user should receive updates from all known users.
-For example, if the user @alice:example.org
is passed to this method, and the Set
-{"@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.
-Porting an existing module that uses the old interface
-In order to port a module that uses Synapse's old module interface, its author needs to:
-
-- ensure the module's callbacks are all asynchronous.
-- register their callbacks using one or more of the
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).
-Example
-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
+Example
+The example below is a module that implements both presence router callbacks, and ensures
+that @alice:example.org
receives all presence updates from @bob:example.com
and
+@charlie:somewhere.org
, regardless of whether Alice shares a room with any of them.
+from typing import Dict, Iterable, Set, Union
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:
+class CustomPresenceRouter:
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,
+ self.api.register_presence_router_callbacks(
+ get_users_for_states=self.get_users_for_states,
+ get_interested_users=self.get_interested_users,
)
- @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
-
-
-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.
-
-Handling spam in Synapse
-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
-able to be imported by the running Synapse.
-Python spam checker class
-The Python class is instantiated with two objects:
-
-- Any configuration (see below).
-- An instance of
synapse.module_api.ModuleApi
.
-
-It then implements methods which return a boolean to alter behavior in Synapse.
-All the methods must be defined.
-There's a generic method for checking every event (check_event_for_spam
), as
-well as some specific methods:
-
-user_may_invite
-user_may_create_room
-user_may_create_room_alias
-user_may_publish_room
-check_username_for_spam
-check_registration_for_spam
-check_media_file_for_spam
-
-The details of each of these methods (as well as their inputs and outputs)
-are documented in the synapse.events.spamcheck.SpamChecker
class.
-The ModuleApi
class provides a way for the custom spam checker class to
-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.
-Example
-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
-
-Configuration
-Modify the spam_checker
section of your homeserver.yaml
in the following
-manner:
-Create a list entry with the keys module
and config
.
-
--
-
module
should point to the fully qualified Python class that implements your
-custom logic, e.g. my_module.ExampleSpamChecker
.
-
--
-
config
is a dictionary that gets passed to the spam checker class.
-
-
-Example
-This section might look like:
-spam_checker:
- - module: my_module.ExampleSpamChecker
- config:
- # Enable or disable a specific option in ExampleSpamChecker.
- my_custom_option: true
-
-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.
-Examples
-The Mjolnir project is a full fledged
-example using the Synapse spam checking API, including a bot for dynamic
-configuration.
-
-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
-this page.
-
-Presence Router Module
-Synapse supports configuring a module that can specify additional users
-(local or remote) to should receive certain presence updates from local
-users.
-Note that routing presence via Application Service transactions is not
-currently supported.
-The presence routing module is implemented as a Python class, which will
-be imported by the running Synapse.
-Python Presence Router Class
-The Python class is instantiated with two objects:
-
-- A configuration object of some type (see below).
-- An instance of
synapse.module_api.ModuleApi
.
-
-It then implements methods related to presence routing.
-Note that one method of ModuleApi
that may be useful is:
-async def ModuleApi.send_local_online_presence_to(users: Iterable[str]) -> None
-
-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 presence stream.
-By default, this is the main process, but another worker can be configured to do
-so.
-Module structure
-Below is a list of possible methods that can be implemented, and whether they are
-required.
-parse_config
-def parse_config(config_dict: dict) -> Any
-
-Required. A static method that is passed a dictionary of config options, and
-should return a validated config object. This method is described further in
-Configuration.
-get_users_for_states
-async def get_users_for_states(
- self,
- state_updates: Iterable[UserPresenceState],
-) -> Dict[str, Set[UserPresenceState]]:
-
-Required. 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 synapse.handlers.presence.UserPresenceState
instances.
-Synapse will then attempt to send the specified presence updates to each user when
-possible.
-get_interested_users
-async def get_interested_users(self, user_id: str) -> Union[Set[str], str]
-
-Required. 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.
-This method should return a python set of Matrix User IDs, or the object
-synapse.events.presence_router.PresenceRouter.ALL_USERS
to indicate that the passed
-user should receive presence information for all known users.
-For clarity, if the user @alice:example.org
is passed to this method, and the Set
-{"@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
-Below is an example implementation of a presence router class.
-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:
- """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.
-
- Args:
- config: A configuration object.
- module_api: An instance of Synapse's ModuleApi.
- """
- def __init__(self, config: PresenceRouterConfig, module_api: ModuleApi):
- self._config = config
- self._module_api = module_api
-
- @staticmethod
- def parse_config(config_dict: dict) -> PresenceRouterConfig:
- """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.
- """
- # Initialise a typed config object
- config = PresenceRouterConfig()
- always_send_to_users = config_dict.get("always_send_to_users")
- blacklisted_users = config_dict.get("blacklisted_users")
-
- # 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
-
async def get_users_for_states(
self,
- state_updates: Iterable[UserPresenceState],
- ) -> Dict[str, Set[UserPresenceState]]:
- """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 -> set of UserPresenceState that the user should
- receive.
- """
- destination_users = {} # type: Dict[str, Set[UserPresenceState]
-
- # Ignore any updates for blacklisted users
- desired_updates = set()
+ state_updates: Iterable["synapse.api.UserPresenceState"],
+ ) -> Dict[str, Set["synapse.api.UserPresenceState"]]:
+ res = {}
for update in state_updates:
- if update.state_key not in self._config.blacklisted_users:
- desired_updates.add(update)
+ if (
+ update.user_id == "@bob:example.com"
+ or update.user_id == "@charlie:somewhere.org"
+ ):
+ res.setdefault("@alice:example.com", 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,
- ) -> Union[Set[str], PresenceRouter.ALL_USERS]:
- """
- 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.
- """
- if user_id in self._config.always_send_to_users:
- return PresenceRouter.ALL_USERS
+ ) -> Union[Set[str], "synapse.module_api.PRESENCE_ALL_USERS"]:
+ if user_id == "@alice:example.com":
+ return {"@bob:example.com", "@charlie:somewhere.org"}
return set()
-A note on get_users_for_states
and get_interested_users
-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.
-get_users_for_states
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.
-In contrast, get_interested_users
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 get_users_for_states
once again, with only the syncing user's
-routing information pulled from the resulting dictionary.
-Their routing logic should thus line up, else you may run into unintended behaviour.
-Configuration
-Once you've crafted your module and installed it into the same Python environment as
-Synapse, amend your homeserver config file with the following.
-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: ["@presence_gobbler:example.org"]
- blacklisted_users:
- - "@alice:example.com"
- - "@bob:example.com"
- ...
-
-The contents of config
will be passed as a Python dictionary to the static
-parse_config
method of your class. The object returned by this method will
-then be passed to the __init__
method of your module as config
.
+Account validity callbacks
+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
+register_account_validity_callbacks
method.
+The available account validity callbacks are:
+is_user_expired
+async def is_user_expired(user: str) -> Optional[bool]
+
+Called when processing any authenticated request (except for logout requests). The module
+can return a bool
to indicate whether the user has expired and should be locked out of
+their account, or None
if the module wasn't able to figure it out. The user is
+represented by their Matrix user ID (e.g. @alice:example.com
).
+If the module returns True
, the current request will be denied with the error code
+ORG_MATRIX_EXPIRED_ACCOUNT
and the HTTP status code 403. Note that this doesn't
+invalidate the user's access token.
+on_user_registration
+async def on_user_registration(user: str) -> None
+
+Called after successfully registering a user, in case the module needs to perform extra
+operations to keep track of them. (e.g. add them to a database table). The user is
+represented by their Matrix user ID.
+Porting an existing module that uses the old interface
+In order to port a module that uses Synapse's old module interface, its author needs to:
+
+- ensure the module's callbacks are all asynchronous.
+- register their callbacks using one or more of the
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).
Scaling synapse via workers
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
@@ -8274,6 +8112,8 @@ expressions:
^/_matrix/federation/v1/user/devices/
^/_matrix/federation/v1/get_groups_publicised$
^/_matrix/key/v2/query
+^/_matrix/federation/unstable/org.matrix.msc2946/spaces/
+^/_matrix/federation/unstable/org.matrix.msc2946/hierarchy/
# Inbound federation transaction request
^/_matrix/federation/v1/send/
@@ -8285,6 +8125,9 @@ expressions:
^/_matrix/client/(api/v1|r0|unstable)/rooms/.*/context/.*$
^/_matrix/client/(api/v1|r0|unstable)/rooms/.*/members$
^/_matrix/client/(api/v1|r0|unstable)/rooms/.*/state$
+^/_matrix/client/unstable/org.matrix.msc2946/rooms/.*/spaces$
+^/_matrix/client/unstable/org.matrix.msc2946/rooms/.*/hierarchy$
+^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary$
^/_matrix/client/(api/v1|r0|unstable)/account/3pid$
^/_matrix/client/(api/v1|r0|unstable)/devices$
^/_matrix/client/(api/v1|r0|unstable)/keys/query$
@@ -9908,34 +9751,51 @@ clients in a confused state.
the old room to the new.
new_room_id
- A string representing the room ID of the new room.
-Undoing room shutdowns
-Note: This guide may be outdated by the time you read it. By nature of room shutdowns being performed at the database level,
+
Undoing room deletions
+Note: This guide may be outdated by the time you read it. By nature of room deletions being performed at the database level,
the structure can and does change without notice.
-First, it's important to understand that a room shutdown is very destructive. Undoing a shutdown is not as simple as pretending it
+
First, it's important to understand that a room deletion is very destructive. Undoing a deletion is not as simple as pretending it
never happened - work has to be done to move forward instead of resetting the past. In fact, in some cases it might not be possible
to recover at all:
- If the room was invite-only, your users will need to be re-invited.
- If the room no longer has any members at all, it'll be impossible to rejoin.
-- The first user to rejoin will have to do so via an alias on a different server.
+- The first user to rejoin will have to do so via an alias on a different
+server (or receive an invite from a user on a different server).
With all that being said, if you still want to try and recover the room:
+-
+
If the room was block
ed, you must unblock it on your server. This can be
+accomplished as follows:
+
- For safety reasons, shut down Synapse.
- In the database, run
DELETE FROM blocked_rooms WHERE room_id = '!example:example.org';
- For caution: it's recommended to run this in a transaction:
BEGIN; DELETE ...;
, verify you got 1 result, then COMMIT;
.
-- The room ID is the same one supplied to the shutdown room API, not the Content Violation room.
+- The room ID is the same one supplied to the delete room API, not the Content Violation room.
- Restart Synapse.
-You will have to manually handle, if you so choose, the following:
-
-- Aliases that would have been redirected to the Content Violation room.
-- Users that would have been booted from the room (and will have been force-joined to the Content Violation room).
-- Removal of the Content Violation room if desired.
-
+This step is unnecessary if block
was not set.
+
+-
+
Any room aliases on your server that pointed to the deleted room may have
+been deleted, or redirected to the Content Violation room. These will need
+to be restored manually.
+
+-
+
Users on your server that were in the deleted room will have been kicked
+from the room. Consider whether you want to update their membership
+(possibly via the Edit Room Membership API) or let
+them handle rejoining themselves.
+
+-
+
If new_room_user_id
was given, a 'Content Violation' will have been
+created. Consider whether you want to delete that roomm.
+
+
Deprecated endpoint
The previous deprecated API will be removed in a future release, it was:
POST /_synapse/admin/v1/rooms/<room_id>/delete
@@ -9953,7 +9813,7 @@ optionally be specified, e.g.:
Forward Extremities Admin API
Enables querying and deleting forward extremities from rooms. When a lot of forward
-extremities accumulate in a room, performance can become degraded. For details, see
+extremities accumulate in a room, performance can become degraded. For details, see
#1760.
Check for forward extremities
To check the status of forward extremities for a room:
@@ -9973,7 +9833,7 @@ extremities accumulate in a room, performance can become degraded. For details,
}
Deleting forward extremities
-WARNING: Please ensure you know what you're doing and have read
+
WARNING: Please ensure you know what you're doing and have read
the related issue #1760.
Under no situations should this API be executed as an automated maintenance task!
If a room has lots of forward extremities, the extra can be
@@ -11142,7 +11002,7 @@ debugging.
Note that this will give administrative access to synapse to all users with
shell access to the server. It should therefore not be enabled in
environments where untrusted users have shell access.
-
+Configuring the manhole
To enable it, first uncomment the manhole
listener configuration in
homeserver.yaml
. The configuration is slightly different if you're using docker.
Docker config
@@ -11170,12 +11030,28 @@ The bind_addresses
in the example below is important: it ensures th
bind_addresses: ['::1', '127.0.0.1']
type: manhole
-Accessing synapse manhole
+Security settings
+The following config options are available:
+
+username
- The username for the manhole (defaults to matrix
)
+password
- The password for the manhole (defaults to rabbithole
)
+ssh_priv_key
- The path to a private SSH key (defaults to a hardcoded value)
+ssh_pub_key
- The path to a public SSH key (defaults to a hardcoded value)
+
+For example:
+manhole_settings:
+ username: manhole
+ password: mypassword
+ ssh_priv_key: "/home/synapse/manhole_keys/id_rsa"
+ ssh_pub_key: "/home/synapse/manhole_keys/id_rsa.pub"
+
+Accessing synapse manhole
Then restart synapse, and point an ssh client at port 9000 on localhost, using
-the username matrix
:
+the username and password configured in homeserver.yaml
- with the default
+configuration, this would be:
ssh -p9000 matrix@localhost
-The password is rabbithole
.
+Then enter the password when prompted (the default is rabbithole
).
This gives a Python REPL in which hs
gives access to the
synapse.server.HomeServer
object - which in turn gives access to many other
parts of the process.
@@ -12275,7 +12151,7 @@ and add it to each log record.
Logcontexts are also used for CPU and database accounting, so that we
can track which requests were responsible for high CPU use or database
activity.
-The synapse.logging.context
module provides a facilities for managing
+
The synapse.logging.context
module provides facilities for managing
the current log context (as well as providing the LoggingContextFilter
class).
Asynchronous functions make the whole thing complicated, so this document describes
@@ -12552,7 +12428,7 @@ and the awaitable chain is now orphaned, and will be garbage-collected at
some point. Note that await_something_interesting
is a coroutine,
which Python implements as a generator function. When Python
garbage-collects generator functions, it gives them a chance to
-clean up by making the async
(or yield
) raise a GeneratorExit
+clean up by making the await
(or yield
) raise a GeneratorExit
exception. In our case, that means that the __exit__
handler of
PreserveLoggingContext
will carefully restore the request context, but
there is now nothing waiting for its return, so the request context is
@@ -12693,7 +12569,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:
@@ -12989,7 +12865,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
@@ -13057,7 +12933,7 @@ sqlite3 database indexed by media_id
.
remote content is assigned a local "filesystem_id"
to ensure that the
directory structure "remote_content/server_name/aa/bb/ccccccccdddddddddddd"
is appropriate. Thumbnails for remote content are stored under
-"remote_thumbnails/server_name/..."
+"remote_thumbnail/server_name/..."
Room and User Statistics
Synapse maintains room and user statistics in various tables. These can be used
for administrative purposes but are also used when generating the public room
--
cgit 1.5.1