feat(matrix): make e2ee configurable with enabled default
This commit is contained in:
@@ -254,7 +254,7 @@ class MatrixChannel(BaseChannel):
|
|||||||
store_path=store_path, # Where tokens are saved
|
store_path=store_path, # Where tokens are saved
|
||||||
config=AsyncClientConfig(
|
config=AsyncClientConfig(
|
||||||
store_sync_tokens=True, # Auto-persists next_batch tokens
|
store_sync_tokens=True, # Auto-persists next_batch tokens
|
||||||
encryption_enabled=True,
|
encryption_enabled=self.config.e2ee_enabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -265,6 +265,14 @@ class MatrixChannel(BaseChannel):
|
|||||||
self._register_event_callbacks()
|
self._register_event_callbacks()
|
||||||
self._register_response_callbacks()
|
self._register_response_callbacks()
|
||||||
|
|
||||||
|
if self.config.e2ee_enabled:
|
||||||
|
logger.info("Matrix E2EE is enabled.")
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"Matrix E2EE is disabled; encrypted room messages may be undecryptable and "
|
||||||
|
"encrypted-device verification is not applied on send."
|
||||||
|
)
|
||||||
|
|
||||||
if self.config.device_id:
|
if self.config.device_id:
|
||||||
try:
|
try:
|
||||||
self.client.load_store()
|
self.client.load_store()
|
||||||
@@ -316,13 +324,17 @@ class MatrixChannel(BaseChannel):
|
|||||||
if not self.client:
|
if not self.client:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
room_send_kwargs: dict[str, Any] = {
|
||||||
|
"room_id": msg.chat_id,
|
||||||
|
"message_type": "m.room.message",
|
||||||
|
"content": _build_matrix_text_content(msg.content),
|
||||||
|
}
|
||||||
|
if self.config.e2ee_enabled:
|
||||||
|
# TODO(matrix): Add explicit config for strict verified-device sending mode.
|
||||||
|
room_send_kwargs["ignore_unverified_devices"] = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.client.room_send(
|
await self.client.room_send(**room_send_kwargs)
|
||||||
room_id=msg.chat_id,
|
|
||||||
message_type="m.room.message",
|
|
||||||
content=_build_matrix_text_content(msg.content),
|
|
||||||
ignore_unverified_devices=True,
|
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
await self._stop_typing_keepalive(msg.chat_id, clear_typing=True)
|
await self._stop_typing_keepalive(msg.chat_id, clear_typing=True)
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ class MatrixConfig(Base):
|
|||||||
access_token: str = ""
|
access_token: str = ""
|
||||||
user_id: str = "" # @bot:matrix.org
|
user_id: str = "" # @bot:matrix.org
|
||||||
device_id: str = ""
|
device_id: str = ""
|
||||||
|
# Enable Matrix E2EE support (encryption + encrypted room handling).
|
||||||
|
e2ee_enabled: bool = True
|
||||||
# Max seconds to wait for sync_forever to stop gracefully before cancellation fallback.
|
# Max seconds to wait for sync_forever to stop gracefully before cancellation fallback.
|
||||||
sync_stop_grace_seconds: int = 2
|
sync_stop_grace_seconds: int = 2
|
||||||
# Max attachment size accepted from inbound Matrix media events.
|
# Max attachment size accepted from inbound Matrix media events.
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from nanobot.channels.matrix import (
|
|||||||
)
|
)
|
||||||
from nanobot.config.schema import MatrixConfig
|
from nanobot.config.schema import MatrixConfig
|
||||||
|
|
||||||
|
_ROOM_SEND_UNSET = object()
|
||||||
|
|
||||||
|
|
||||||
class _DummyTask:
|
class _DummyTask:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -73,16 +75,16 @@ class _FakeAsyncClient:
|
|||||||
room_id: str,
|
room_id: str,
|
||||||
message_type: str,
|
message_type: str,
|
||||||
content: dict[str, object],
|
content: dict[str, object],
|
||||||
ignore_unverified_devices: bool,
|
ignore_unverified_devices: object = _ROOM_SEND_UNSET,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.room_send_calls.append(
|
call: dict[str, object] = {
|
||||||
{
|
"room_id": room_id,
|
||||||
"room_id": room_id,
|
"message_type": message_type,
|
||||||
"message_type": message_type,
|
"content": content,
|
||||||
"content": content,
|
}
|
||||||
"ignore_unverified_devices": ignore_unverified_devices,
|
if ignore_unverified_devices is not _ROOM_SEND_UNSET:
|
||||||
}
|
call["ignore_unverified_devices"] = ignore_unverified_devices
|
||||||
)
|
self.room_send_calls.append(call)
|
||||||
if self.raise_on_send:
|
if self.raise_on_send:
|
||||||
raise RuntimeError("send failed")
|
raise RuntimeError("send failed")
|
||||||
|
|
||||||
@@ -149,6 +151,7 @@ async def test_start_skips_load_store_when_device_id_missing(
|
|||||||
await channel.start()
|
await channel.start()
|
||||||
|
|
||||||
assert len(clients) == 1
|
assert len(clients) == 1
|
||||||
|
assert clients[0].config.encryption_enabled is True
|
||||||
assert clients[0].load_store_called is False
|
assert clients[0].load_store_called is False
|
||||||
assert len(clients[0].callbacks) == 3
|
assert len(clients[0].callbacks) == 3
|
||||||
assert len(clients[0].response_callbacks) == 3
|
assert len(clients[0].response_callbacks) == 3
|
||||||
@@ -156,6 +159,40 @@ async def test_start_skips_load_store_when_device_id_missing(
|
|||||||
await channel.stop()
|
await channel.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_start_disables_e2ee_when_configured(
|
||||||
|
monkeypatch, tmp_path
|
||||||
|
) -> None:
|
||||||
|
clients: list[_FakeAsyncClient] = []
|
||||||
|
|
||||||
|
def _fake_client(*args, **kwargs) -> _FakeAsyncClient:
|
||||||
|
client = _FakeAsyncClient(*args, **kwargs)
|
||||||
|
clients.append(client)
|
||||||
|
return client
|
||||||
|
|
||||||
|
def _fake_create_task(coro):
|
||||||
|
coro.close()
|
||||||
|
return _DummyTask()
|
||||||
|
|
||||||
|
monkeypatch.setattr("nanobot.channels.matrix.get_data_dir", lambda: tmp_path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"nanobot.channels.matrix.AsyncClientConfig",
|
||||||
|
lambda **kwargs: SimpleNamespace(**kwargs),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr("nanobot.channels.matrix.AsyncClient", _fake_client)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"nanobot.channels.matrix.asyncio.create_task", _fake_create_task
|
||||||
|
)
|
||||||
|
|
||||||
|
channel = MatrixChannel(_make_config(device_id="", e2ee_enabled=False), MessageBus())
|
||||||
|
await channel.start()
|
||||||
|
|
||||||
|
assert len(clients) == 1
|
||||||
|
assert clients[0].config.encryption_enabled is False
|
||||||
|
|
||||||
|
await channel.stop()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_stop_stops_sync_forever_before_close(monkeypatch) -> None:
|
async def test_stop_stops_sync_forever_before_close(monkeypatch) -> None:
|
||||||
channel = MatrixChannel(_make_config(device_id="DEVICE"), MessageBus())
|
channel = MatrixChannel(_make_config(device_id="DEVICE"), MessageBus())
|
||||||
@@ -632,9 +669,24 @@ async def test_send_clears_typing_after_send() -> None:
|
|||||||
"body": "Hi",
|
"body": "Hi",
|
||||||
"m.mentions": {},
|
"m.mentions": {},
|
||||||
}
|
}
|
||||||
|
assert client.room_send_calls[0]["ignore_unverified_devices"] is True
|
||||||
assert client.typing_calls[-1] == ("!room:matrix.org", False, TYPING_NOTICE_TIMEOUT_MS)
|
assert client.typing_calls[-1] == ("!room:matrix.org", False, TYPING_NOTICE_TIMEOUT_MS)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_send_omits_ignore_unverified_devices_when_e2ee_disabled() -> None:
|
||||||
|
channel = MatrixChannel(_make_config(e2ee_enabled=False), MessageBus())
|
||||||
|
client = _FakeAsyncClient("", "", "", None)
|
||||||
|
channel.client = client
|
||||||
|
|
||||||
|
await channel.send(
|
||||||
|
OutboundMessage(channel="matrix", chat_id="!room:matrix.org", content="Hi")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(client.room_send_calls) == 1
|
||||||
|
assert "ignore_unverified_devices" not in client.room_send_calls[0]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_send_stops_typing_keepalive_task() -> None:
|
async def test_send_stops_typing_keepalive_task() -> None:
|
||||||
channel = MatrixChannel(_make_config(), MessageBus())
|
channel = MatrixChannel(_make_config(), MessageBus())
|
||||||
|
|||||||
Reference in New Issue
Block a user