From 7a927aac59bad94111ef585dc9770bfc92280e6d Mon Sep 17 00:00:00 2001 From: DMRobertson Date: Tue, 21 Sep 2021 13:05:07 +0000 Subject: deploy: 9ffa787eb243c98a6ca1ecd9eac4a6b5dac2bef0 --- latest/print.html | 888 +++++++++++++++++++++++------------------------------- 1 file changed, 382 insertions(+), 506 deletions(-) (limited to 'latest/print.html') diff --git a/latest/print.html b/latest/print.html index 85964f0f41..dc2b03ddb4 100644 --- a/latest/print.html +++ b/latest/print.html @@ -101,7 +101,7 @@ @@ -1487,6 +1487,12 @@ dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb +

Upgrading to v1.43.0

+

The spaces summary APIs can now be handled by workers

+

The available worker applications documentation +has been updated to reflect that calls to the /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

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.

URI that clients use to connect to the server. (It is used to construct consent_uri in the error.)

URL Previews

-

Design notes on a URL previewing service for Matrix:

-

Options are:

-
    -
  1. Have an AS which listens for URLs, downloads them, and inserts an event that describes their metadata.
  2. -
- -
    -
  1. Have a generic preview API (nothing to do with Matrix) that provides a previewing service:
  2. -
+

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:

+
  • Returns the result.
  • + +

    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.

    User Directory API Implementation

    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.

    +

    Data model

    +

    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:

    + +

    Message retention policies

    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.

    -

    Writing a module

    +configuring modules in another part of Synapse's configuration file.

    +

    Currently, only the following pre-existing interfaces are compatible with this new system:

    + +

    Writing a module

    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's parse_config static method (see b configuration associated with the module in Synapse's configuration file.

    See the documentation for the ModuleApi class here.

    -

    Handling the module's configuration

    +

    Handling the module's configuration

    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

    -

    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:

      +
    1. +

      If the room was blocked, you must unblock it on your server. This can be +accomplished as follows:

      +
      1. For safety reasons, shut down Synapse.
      2. 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.
      3. 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.

      +
    2. +
    3. +

      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.

      +
    4. +
    5. +

      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.

      +
    6. +
    7. +

      If new_room_user_id was given, a 'Content Violation' will have been +created. Consider whether you want to delete that roomm.

      +
    8. +

    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