From 8fe3c9eba60b4cd48c12a44e0c68e22c4e4abbca Mon Sep 17 00:00:00 2001 From: Marco Alvarez Date: Fri, 10 Jan 2025 21:02:09 +0100 Subject: [PATCH 1/4] add support for MSC4190 --- mautrix/appservice/appservice.py | 3 ++- mautrix/appservice/as_handler.py | 3 +++ mautrix/bridge/bridge.py | 1 + mautrix/bridge/config.py | 4 ++++ mautrix/bridge/e2ee.py | 33 ++++++++++++++++++---------- mautrix/client/api/authentication.py | 17 ++++++++++++++ 6 files changed, 49 insertions(+), 12 deletions(-) diff --git a/mautrix/appservice/appservice.py b/mautrix/appservice/appservice.py index 65e202ae..962c1bdc 100644 --- a/mautrix/appservice/appservice.py +++ b/mautrix/appservice/appservice.py @@ -80,12 +80,13 @@ def __init__( state_store: ASStateStore = None, aiohttp_params: dict = None, ephemeral_events: bool = False, + msc4190: bool = False, encryption_events: bool = False, default_ua: str = HTTPAPI.default_ua, default_http_retry_count: int = 0, connection_limit: int | None = None, ) -> None: - super().__init__(ephemeral_events=ephemeral_events, encryption_events=encryption_events) + super().__init__(ephemeral_events=ephemeral_events, msc4190=msc4190, encryption_events=encryption_events) self.server = server self.domain = domain self.id = id diff --git a/mautrix/appservice/as_handler.py b/mautrix/appservice/as_handler.py index ec7e339f..031d1709 100644 --- a/mautrix/appservice/as_handler.py +++ b/mautrix/appservice/as_handler.py @@ -36,6 +36,7 @@ class AppServiceServerMixin: hs_token: str ephemeral_events: bool + msc4190: bool encryption_events: bool synchronous_handlers: bool @@ -51,6 +52,7 @@ class AppServiceServerMixin: def __init__( self, ephemeral_events: bool = False, + msc4190: bool = False, encryption_events: bool = False, log: logging.Logger | None = None, hs_token: str | None = None, @@ -65,6 +67,7 @@ def __init__( self.otk_handler = None self.device_list_handler = None self.ephemeral_events = ephemeral_events + self.msc4190 = msc4190 self.encryption_events = encryption_events self.synchronous_handlers = False diff --git a/mautrix/bridge/bridge.py b/mautrix/bridge/bridge.py index 9ce9360e..83b3bf08 100644 --- a/mautrix/bridge/bridge.py +++ b/mautrix/bridge/bridge.py @@ -176,6 +176,7 @@ def prepare_appservice(self) -> None: tls_key=self.config.get("appservice.tls_key", None), bot_localpart=self.config["appservice.bot_username"], ephemeral_events=self.config["appservice.ephemeral_events"], + msc4190=self.config["appservice.msc4190"], encryption_events=self.config["bridge.encryption.appservice"], default_ua=HTTPAPI.default_ua, default_http_retry_count=default_http_retry_count, diff --git a/mautrix/bridge/config.py b/mautrix/bridge/config.py index b98ebc52..c725d530 100644 --- a/mautrix/bridge/config.py +++ b/mautrix/bridge/config.py @@ -132,6 +132,7 @@ def do_update(self, helper: ConfigUpdateHelper) -> None: copy("appservice.hs_token") copy("appservice.ephemeral_events") + copy("appservice.msc4190") copy("bridge.management_room_text.welcome") copy("bridge.management_room_text.welcome_connected") @@ -241,3 +242,6 @@ def generate_registration(self) -> None: if self["appservice.ephemeral_events"]: self._registration["de.sorunome.msc2409.push_ephemeral"] = True self._registration["push_ephemeral"] = True + + if self["appservice.msc4190"]: + self._registration["io.element.msc4190"] = True diff --git a/mautrix/bridge/e2ee.py b/mautrix/bridge/e2ee.py index 7ae66abf..ef8f8494 100644 --- a/mautrix/bridge/e2ee.py +++ b/mautrix/bridge/e2ee.py @@ -246,7 +246,7 @@ async def decrypt(self, evt: EncryptedEvent, wait_session_timeout: int = 5) -> M async def start(self) -> None: flows = await self.client.get_login_flows() - if not flows.supports_type(LoginType.APPSERVICE): + if not self.az.msc4190 and not flows.supports_type(LoginType.APPSERVICE): self.log.critical( "Encryption enabled in config, but homeserver does not support appservice login" ) @@ -261,16 +261,27 @@ async def start(self) -> None: device_id = await self.crypto_store.get_device_id() if device_id: self.log.debug(f"Found device ID in database: {device_id}") - # We set the API token to the AS token here to authenticate the appservice login - # It'll get overridden after the login - self.client.api.token = self.az.as_token - await self.client.login( - login_type=LoginType.APPSERVICE, - device_name=self.device_name, - device_id=device_id, - store_access_token=True, - update_hs_url=False, - ) + + if self.az.msc4190: + if not device_id: + self.log.debug("Creating bot device with msc4190") + self.client.api.token = self.az.as_token + await self.client.create_device_msc4190( + device_id=device_id, + initial_display_name=self.device_name + ) + else: + # We set the API token to the AS token here to authenticate the appservice login + # It'll get overridden after the login + self.client.api.token = self.az.as_token + await self.client.login( + login_type=LoginType.APPSERVICE, + device_name=self.device_name, + device_id=device_id, + store_access_token=True, + update_hs_url=False, + ) + await self.crypto.load() if not device_id: await self.crypto_store.put_device_id(self.client.device_id) diff --git a/mautrix/client/api/authentication.py b/mautrix/client/api/authentication.py index cd77272d..5e856072 100644 --- a/mautrix/client/api/authentication.py +++ b/mautrix/client/api/authentication.py @@ -5,6 +5,8 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from __future__ import annotations +import secrets + from mautrix.api import Method, Path from mautrix.errors import MatrixResponseError from mautrix.types import ( @@ -117,6 +119,21 @@ async def login( self.api.base_url = base_url.rstrip("/") return resp_data + async def create_device_msc4190( + self, + device_id: str, + initial_display_name: str + ) -> None: + """ + Create a Device for a user of the homeserver using appservice interface defined in MSC4190 + """ + if len(device_id) == 0: + device_id = DeviceID(secrets.token_urlsafe(10)) + self.api.as_user_id = self.mxid + await self.api.request(Method.PUT, Path.v3.devices[device_id], { "display_name": initial_display_name}) + self.api.as_device_id = device_id + self.device_id = device_id + async def logout(self, clear_access_token: bool = True) -> None: """ Invalidates an existing access token, so that it can no longer be used for authorization. From 4ddeac98110de48a2a4c0c7cba563065addc11e1 Mon Sep 17 00:00:00 2001 From: Marco Alvarez Date: Fri, 10 Jan 2025 22:08:34 +0100 Subject: [PATCH 2/4] move msc4190 setting to bridge.encryption --- mautrix/bridge/bridge.py | 2 +- mautrix/bridge/config.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mautrix/bridge/bridge.py b/mautrix/bridge/bridge.py index 83b3bf08..3ed3a07c 100644 --- a/mautrix/bridge/bridge.py +++ b/mautrix/bridge/bridge.py @@ -176,7 +176,7 @@ def prepare_appservice(self) -> None: tls_key=self.config.get("appservice.tls_key", None), bot_localpart=self.config["appservice.bot_username"], ephemeral_events=self.config["appservice.ephemeral_events"], - msc4190=self.config["appservice.msc4190"], + msc4190=self.config["bridge.encryption.msc4190"], encryption_events=self.config["bridge.encryption.appservice"], default_ua=HTTPAPI.default_ua, default_http_retry_count=default_http_retry_count, diff --git a/mautrix/bridge/config.py b/mautrix/bridge/config.py index c725d530..6127d5db 100644 --- a/mautrix/bridge/config.py +++ b/mautrix/bridge/config.py @@ -132,7 +132,6 @@ def do_update(self, helper: ConfigUpdateHelper) -> None: copy("appservice.hs_token") copy("appservice.ephemeral_events") - copy("appservice.msc4190") copy("bridge.management_room_text.welcome") copy("bridge.management_room_text.welcome_connected") @@ -144,6 +143,7 @@ def do_update(self, helper: ConfigUpdateHelper) -> None: copy("bridge.encryption.default") copy("bridge.encryption.require") copy("bridge.encryption.appservice") + copy("bridge.encryption.msc4190") copy("bridge.encryption.delete_keys.delete_outbound_on_ack") copy("bridge.encryption.delete_keys.dont_store_outbound") copy("bridge.encryption.delete_keys.ratchet_on_decrypt") @@ -243,5 +243,5 @@ def generate_registration(self) -> None: self._registration["de.sorunome.msc2409.push_ephemeral"] = True self._registration["push_ephemeral"] = True - if self["appservice.msc4190"]: + if self["bridge.encryption.msc4190"]: self._registration["io.element.msc4190"] = True From 20a5e34f659336799418dbe1cf5d73f4c33c4abd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 15 Jan 2025 18:07:44 +0200 Subject: [PATCH 3/4] Move msc4190 flag to correct place --- mautrix/appservice/appservice.py | 3 +-- mautrix/appservice/as_handler.py | 3 --- mautrix/bridge/bridge.py | 1 - mautrix/bridge/e2ee.py | 13 +++++++------ 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/mautrix/appservice/appservice.py b/mautrix/appservice/appservice.py index 962c1bdc..65e202ae 100644 --- a/mautrix/appservice/appservice.py +++ b/mautrix/appservice/appservice.py @@ -80,13 +80,12 @@ def __init__( state_store: ASStateStore = None, aiohttp_params: dict = None, ephemeral_events: bool = False, - msc4190: bool = False, encryption_events: bool = False, default_ua: str = HTTPAPI.default_ua, default_http_retry_count: int = 0, connection_limit: int | None = None, ) -> None: - super().__init__(ephemeral_events=ephemeral_events, msc4190=msc4190, encryption_events=encryption_events) + super().__init__(ephemeral_events=ephemeral_events, encryption_events=encryption_events) self.server = server self.domain = domain self.id = id diff --git a/mautrix/appservice/as_handler.py b/mautrix/appservice/as_handler.py index 031d1709..ec7e339f 100644 --- a/mautrix/appservice/as_handler.py +++ b/mautrix/appservice/as_handler.py @@ -36,7 +36,6 @@ class AppServiceServerMixin: hs_token: str ephemeral_events: bool - msc4190: bool encryption_events: bool synchronous_handlers: bool @@ -52,7 +51,6 @@ class AppServiceServerMixin: def __init__( self, ephemeral_events: bool = False, - msc4190: bool = False, encryption_events: bool = False, log: logging.Logger | None = None, hs_token: str | None = None, @@ -67,7 +65,6 @@ def __init__( self.otk_handler = None self.device_list_handler = None self.ephemeral_events = ephemeral_events - self.msc4190 = msc4190 self.encryption_events = encryption_events self.synchronous_handlers = False diff --git a/mautrix/bridge/bridge.py b/mautrix/bridge/bridge.py index 3ed3a07c..9ce9360e 100644 --- a/mautrix/bridge/bridge.py +++ b/mautrix/bridge/bridge.py @@ -176,7 +176,6 @@ def prepare_appservice(self) -> None: tls_key=self.config.get("appservice.tls_key", None), bot_localpart=self.config["appservice.bot_username"], ephemeral_events=self.config["appservice.ephemeral_events"], - msc4190=self.config["bridge.encryption.msc4190"], encryption_events=self.config["bridge.encryption.appservice"], default_ua=HTTPAPI.default_ua, default_http_retry_count=default_http_retry_count, diff --git a/mautrix/bridge/e2ee.py b/mautrix/bridge/e2ee.py index ef8f8494..266c8db9 100644 --- a/mautrix/bridge/e2ee.py +++ b/mautrix/bridge/e2ee.py @@ -57,6 +57,7 @@ class EncryptionManager: appservice_mode: bool periodically_delete_expired_keys: bool delete_outdated_inbound: bool + msc4190: bool bridge: br.Bridge az: AppService @@ -108,6 +109,7 @@ def __init__( self.crypto.send_keys_min_trust = TrustState.parse(verification_levels["receive"]) self.key_sharing_enabled = bridge.config["bridge.encryption.allow_key_sharing"] self.appservice_mode = bridge.config["bridge.encryption.appservice"] + self.msc4190 = bridge.config["bridge.encryption.msc4190"] if self.appservice_mode: self.az.otk_handler = self.crypto.handle_as_otk_counts self.az.device_list_handler = self.crypto.handle_as_device_lists @@ -246,7 +248,7 @@ async def decrypt(self, evt: EncryptedEvent, wait_session_timeout: int = 5) -> M async def start(self) -> None: flows = await self.client.get_login_flows() - if not self.az.msc4190 and not flows.supports_type(LoginType.APPSERVICE): + if not self.msc4190 and not flows.supports_type(LoginType.APPSERVICE): self.log.critical( "Encryption enabled in config, but homeserver does not support appservice login" ) @@ -262,13 +264,12 @@ async def start(self) -> None: if device_id: self.log.debug(f"Found device ID in database: {device_id}") - if self.az.msc4190: + if self.msc4190: if not device_id: - self.log.debug("Creating bot device with msc4190") + self.log.debug("Creating bot device with MSC4190") self.client.api.token = self.az.as_token await self.client.create_device_msc4190( - device_id=device_id, - initial_display_name=self.device_name + device_id=device_id, initial_display_name=self.device_name ) else: # We set the API token to the AS token here to authenticate the appservice login @@ -281,7 +282,7 @@ async def start(self) -> None: store_access_token=True, update_hs_url=False, ) - + await self.crypto.load() if not device_id: await self.crypto_store.put_device_id(self.client.device_id) From 764b1e5632a688671d5acface91f3acd4d0d9cdc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 15 Jan 2025 18:08:15 +0200 Subject: [PATCH 4/4] Fix code style --- mautrix/client/api/authentication.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mautrix/client/api/authentication.py b/mautrix/client/api/authentication.py index 5e856072..0f6249ea 100644 --- a/mautrix/client/api/authentication.py +++ b/mautrix/client/api/authentication.py @@ -119,18 +119,16 @@ async def login( self.api.base_url = base_url.rstrip("/") return resp_data - async def create_device_msc4190( - self, - device_id: str, - initial_display_name: str - ) -> None: + async def create_device_msc4190(self, device_id: str, initial_display_name: str) -> None: """ Create a Device for a user of the homeserver using appservice interface defined in MSC4190 """ if len(device_id) == 0: device_id = DeviceID(secrets.token_urlsafe(10)) self.api.as_user_id = self.mxid - await self.api.request(Method.PUT, Path.v3.devices[device_id], { "display_name": initial_display_name}) + await self.api.request( + Method.PUT, Path.v3.devices[device_id], {"display_name": initial_display_name} + ) self.api.as_device_id = device_id self.device_id = device_id