Skip to content

Commit 12d7c48

Browse files
committed
Switch to appservice login as specified in matrix-org/matrix-spec-proposals#2778
1 parent cc43498 commit 12d7c48

File tree

6 files changed

+49
-30
lines changed

6 files changed

+49
-30
lines changed

mautrix/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = "0.7.3"
1+
__version__ = "0.8.0+dev"
22
__author__ = "Tulir Asokan <tulir@maunium.net>"
33
__all__ = ["api", "appservice", "bridge", "client", "crypto", "errors", "util", "types"]

mautrix/bridge/e2ee.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@
66
from typing import Tuple, Union, Optional, Dict, TYPE_CHECKING
77
import logging
88
import asyncio
9-
import hashlib
10-
import hmac
119

1210
from mautrix.types import (Filter, RoomFilter, EventFilter, RoomEventFilter, StateFilter, EventType,
1311
RoomID, Serializable, JSON, MessageEvent, EncryptedEvent, StateEvent,
14-
EncryptedMegolmEventContent, RequestedKeyInfo, RoomKeyWithheldCode)
12+
EncryptedMegolmEventContent, RequestedKeyInfo, RoomKeyWithheldCode,
13+
LoginType)
1514
from mautrix.appservice import AppService
1615
from mautrix.errors import EncryptionError
1716
from mautrix.client import Client, SyncStore
1817
from mautrix.crypto import (OlmMachine, CryptoStore, StateStore, PgCryptoStore, PickleCryptoStore,
1918
DeviceIdentity, RejectKeyShare, TrustState)
2019
from mautrix.util.logging import TraceLogger
2120

22-
from .crypto_state_store import GetPortalFunc, PgCryptoStateStore, SQLCryptoStateStore
21+
from .crypto_state_store import PgCryptoStateStore, SQLCryptoStateStore
2322

2423
try:
2524
from mautrix.client.state_store.sqlalchemy import UserProfile
@@ -47,23 +46,21 @@ class EncryptionManager:
4746

4847
bridge: 'Bridge'
4948
az: AppService
50-
login_shared_secret: bytes
5149
_id_prefix: str
5250
_id_suffix: str
5351

5452
sync_task: asyncio.Future
5553
_share_session_events: Dict[RoomID, asyncio.Event]
5654

57-
def __init__(self, bridge: 'Bridge', login_shared_secret: str, homeserver_address: str,
58-
user_id_prefix: str, user_id_suffix: str, db_url: str,
59-
key_sharing_config: Dict[str, bool] = None) -> None:
55+
def __init__(self, bridge: 'Bridge', homeserver_address: str, user_id_prefix: str,
56+
user_id_suffix: str, db_url: str, key_sharing_config: Dict[str, bool] = None
57+
) -> None:
6058
self.loop = bridge.loop or asyncio.get_event_loop()
6159
self.bridge = bridge
6260
self.az = bridge.az
6361
self.device_name = bridge.name
6462
self._id_prefix = user_id_prefix
6563
self._id_suffix = user_id_suffix
66-
self.login_shared_secret = login_shared_secret.encode("utf-8")
6764
self._share_session_events = {}
6865
self.key_sharing_config = key_sharing_config or {}
6966
pickle_key = "mautrix.bridge.e2ee"
@@ -161,17 +158,22 @@ async def decrypt(self, evt: EncryptedEvent) -> MessageEvent:
161158
self.log.trace("Decrypted event %s: %s", evt.event_id, decrypted)
162159
return decrypted
163160

161+
async def check_server_support(self) -> bool:
162+
flows = await self.client.get_login_flows()
163+
return flows.supports_type(LoginType.APPSERVICE)
164+
164165
async def start(self) -> None:
165166
self.log.debug("Logging in with bridge bot user")
166-
password = hmac.new(self.login_shared_secret, self.az.bot_mxid.encode("utf-8"),
167-
hashlib.sha512).hexdigest()
168167
if self.crypto_db:
169168
await self.crypto_db.start()
170169
await self.crypto_store.open()
171170
device_id = await self.crypto_store.get_device_id()
172171
if device_id:
173172
self.log.debug(f"Found device ID in database: {device_id}")
174-
await self.client.login(password=password, device_name=self.device_name,
173+
# We set the API token to the AS token here to authenticate the appservice login
174+
# It'll get overridden after the login
175+
self.client.api.token = self.az.as_token
176+
await self.client.login(login_type=LoginType.APPSERVICE, device_name=self.device_name,
175177
device_id=device_id, store_access_token=True, update_hs_url=False)
176178
await self.crypto.load()
177179
if not device_id:

mautrix/bridge/matrix.py

+13-11
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,13 @@ def __init__(self, command_processor: Optional[CommandProcessor] = None,
5757
if self.config["bridge.encryption.allow"]:
5858
if not EncryptionManager:
5959
self.log.error("Encryption enabled in config, but dependencies not installed.")
60-
elif not self.config["bridge.login_shared_secret"]:
61-
self.log.warning("Encryption enabled in config, but login_shared_secret not set.")
62-
else:
63-
self.e2ee = EncryptionManager(
64-
bridge=bridge,
65-
user_id_prefix=self.user_id_prefix, user_id_suffix=self.user_id_suffix,
66-
login_shared_secret=self.config["bridge.login_shared_secret"],
67-
homeserver_address=self.config["homeserver.address"],
68-
db_url=self._get_db_url(bridge.config),
69-
key_sharing_config=self.config["bridge.encryption.key_sharing"])
60+
return
61+
self.e2ee = EncryptionManager(
62+
bridge=bridge,
63+
user_id_prefix=self.user_id_prefix, user_id_suffix=self.user_id_suffix,
64+
homeserver_address=self.config["homeserver.address"],
65+
db_url=self._get_db_url(bridge.config),
66+
key_sharing_config=self.config["bridge.encryption.key_sharing"])
7067

7168
@staticmethod
7269
def _get_db_url(config: 'BaseBridgeConfig') -> str:
@@ -123,7 +120,12 @@ async def init_as_bot(self) -> None:
123120

124121
async def init_encryption(self) -> None:
125122
if self.e2ee:
126-
await self.e2ee.start()
123+
if not await self.e2ee.check_server_support():
124+
self.log.error("Encryption enabled in config, but homeserver does not "
125+
"support appservice login")
126+
self.e2ee = None
127+
else:
128+
await self.e2ee.start()
127129

128130
@staticmethod
129131
async def allow_message(user: 'BaseUser') -> bool:

mautrix/client/api/authentication.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from mautrix.errors import MatrixResponseError
99
from mautrix.api import Method, Path
10-
from mautrix.types import (UserID, LoginType, UserIdentifier, LoginResponse, LoginFlow,
10+
from mautrix.types import (UserID, LoginType, UserIdentifier, LoginResponse, LoginFlowList,
1111
MatrixUserIdentifier)
1212

1313
from .base import BaseClientAPI
@@ -24,7 +24,7 @@ class ClientAuthenticationMethods(BaseClientAPI):
2424
# region 5.5 Login
2525
# API reference: https://matrix.org/docs/spec/client_server/r0.6.1.html#login
2626

27-
async def get_login_flows(self) -> List[LoginFlow]:
27+
async def get_login_flows(self) -> LoginFlowList:
2828
"""
2929
Get login flows supported by the homeserver.
3030
@@ -35,7 +35,7 @@ async def get_login_flows(self) -> List[LoginFlow]:
3535
"""
3636
resp = await self.api.request(Method.GET, Path.login)
3737
try:
38-
return [LoginFlow.deserialize(flow) for flow in resp["flows"]]
38+
return LoginFlowList.deserialize(resp)
3939
except KeyError:
4040
raise MatrixResponseError("`flows` not in response.")
4141

mautrix/types/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from .auth import (LoginType, UserIdentifierType, MatrixUserIdentifier, ThirdPartyIdentifier,
4242
PhoneIdentifier, UserIdentifier, LoginResponse, DiscoveryInformation,
4343
DiscoveryServer, DiscoveryIntegrations, DiscoveryIntegrationServer,
44-
LoginFlow)
44+
LoginFlow, LoginFlowList)
4545
from .crypto import UnsignedDeviceInfo, DeviceKeys, ClaimKeysResponse, QueryKeysResponse
4646
from .media import MediaRepoConfig, MXOpenGraph, OpenGraphVideo, OpenGraphImage, OpenGraphAudio
4747
from .util import (Obj, Lst, SerializerError, Serializable, SerializableEnum, SerializableAttrs,

mautrix/types/auth.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from attr import dataclass
88
import attr
99

10-
from .primitive import UserID, JSON
10+
from .primitive import UserID, DeviceID, JSON
1111
from .util import SerializableAttrs, ExtensibleEnum, deserializer, Obj
1212

1313

@@ -20,6 +20,10 @@ class LoginType(ExtensibleEnum):
2020
"""
2121
PASSWORD: 'LoginType' = "m.login.password"
2222
TOKEN: 'LoginType' = "m.login.token"
23+
SSO: 'LoginType' = "m.login.sso"
24+
25+
JWT: 'LoginType' = "org.matrix.login.jwt"
26+
APPSERVICE: 'LoginType' = "uk.half-shot.msc2778.login.application_service"
2327

2428

2529
@dataclass
@@ -33,6 +37,17 @@ class LoginFlow(SerializableAttrs['LoginFlow']):
3337
type: LoginType
3438

3539

40+
@dataclass
41+
class LoginFlowList(SerializableAttrs['LoginFlowList']):
42+
flows: List[LoginFlow]
43+
44+
def supports_type(self, type: LoginType) -> bool:
45+
for flow in self.flows:
46+
if flow.type == type:
47+
return True
48+
return False
49+
50+
3651
class UserIdentifierType(ExtensibleEnum):
3752
"""
3853
A user identifier type, as specified in the `Identifier types`_ section of the login spec.
@@ -150,6 +165,6 @@ class LoginResponse(SerializableAttrs['LoginResponse']):
150165
https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-login
151166
"""
152167
user_id: UserID
153-
device_id: str
168+
device_id: DeviceID
154169
access_token: str
155170
well_known: DiscoveryInformation = attr.ib(factory=DiscoveryInformation)

0 commit comments

Comments
 (0)