mirror of
https://github.com/element-hq/synapse.git
synced 2026-08-14 09:20:49 +00:00
Designate a device to claim the key bundle on accepted-knock auto-join
A server-initiated join lands on all the user's devices in the same sync instant, so each would otherwise eagerly download the MSC4268 room key bundle. Send the most recently active device an org.matrix.msc4509.key_bundle_claim to-device hint (MSC4509) so exactly one device claims eagerly; the rest defer until first needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uk8aPxHn3BHCe52L226jdG
This commit is contained in:
co-authored by
Claude Fable 5
parent
e53dfc0954
commit
b618cc4b0a
@@ -1 +1 @@
|
||||
Add a new `auto_accept_invites.enabled_for_accepted_knocks` option which, when enabled, automatically joins a user to a room when an invite arrives for a knock they have pending in that room (i.e. their knock was accepted), instead of requiring the user to accept the resulting invite by hand. Also fix the `on_new_event` module callback breaking event persistence when a rejected event was received over federation.
|
||||
Add a new `auto_accept_invites.enabled_for_accepted_knocks` option which, when enabled, automatically joins a user to a room when an invite arrives for a knock they have pending in that room (i.e. their knock was accepted), instead of requiring the user to accept the resulting invite by hand; the server designates one of the user's devices to download any MSC4268 room key bundle. Also fix the `on_new_event` module callback breaking event persistence when a rejected event was received over federation.
|
||||
|
||||
@@ -26,9 +26,14 @@ from synapse.api.constants import AccountDataTypes, EventTypes, Membership
|
||||
from synapse.api.errors import SynapseError
|
||||
from synapse.config.auto_accept_invites import AutoAcceptInvitesConfig
|
||||
from synapse.module_api import EventBase, ModuleApi, run_as_background_process
|
||||
from synapse.types import create_requester
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# MSC4509: to-device message designating one of a user's devices as the eager
|
||||
# downloader of an MSC4268 room key bundle after a server-initiated join.
|
||||
UNSTABLE_KEY_BUNDLE_CLAIM_TYPE = "org.matrix.msc4509.key_bundle_claim"
|
||||
|
||||
|
||||
class InviteAutoAccepter:
|
||||
def __init__(self, config: AutoAcceptInvitesConfig, api: ModuleApi):
|
||||
@@ -251,3 +256,54 @@ class InviteAutoAccepter:
|
||||
|
||||
if join_event is not None:
|
||||
break
|
||||
|
||||
if join_event is not None:
|
||||
# The join was made by the server, so it lands on all of the
|
||||
# user's devices in the same sync instant. Designate one device to
|
||||
# eagerly download any MSC4268 room key bundle for the room, lest
|
||||
# every device does (MSC4509). Best effort: the hint is advisory,
|
||||
# and without it clients fall back to claiming the bundle lazily.
|
||||
try:
|
||||
await self._send_key_bundle_claim_hint(target, room_id)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to send key bundle claim hint to %s for %s: %s",
|
||||
target,
|
||||
room_id,
|
||||
e,
|
||||
)
|
||||
|
||||
async def _send_key_bundle_claim_hint(self, user_id: str, room_id: str) -> None:
|
||||
"""Send an `org.matrix.msc4509.key_bundle_claim` to-device message to
|
||||
the user's most recently active device, designating it as the one
|
||||
device which should eagerly download an MSC4268 room key bundle for
|
||||
the room the user was just joined to.
|
||||
|
||||
Args:
|
||||
user_id: the (local) user who was joined to the room
|
||||
room_id: the room they were joined to
|
||||
"""
|
||||
# This is bundled with Synapse (rather than a true module), so reach
|
||||
# into the homeserver for the device machinery: the module API doesn't
|
||||
# expose device listing or to-device sending.
|
||||
hs = self._api._hs
|
||||
devices = await hs.get_device_handler().get_devices_by_user(user_id)
|
||||
if not devices:
|
||||
return
|
||||
|
||||
# Pick the device the user is most likely to be actively using. The
|
||||
# worst a stale pick costs is the bundle being claimed lazily instead.
|
||||
device = max(devices, key=lambda d: d.get("last_seen_ts") or 0)
|
||||
device_id = device["device_id"]
|
||||
|
||||
logger.info(
|
||||
"Designating device %s of %s to claim any key bundle for %s",
|
||||
device_id,
|
||||
user_id,
|
||||
room_id,
|
||||
)
|
||||
await hs.get_device_message_handler().send_device_message(
|
||||
create_requester(user_id),
|
||||
UNSTABLE_KEY_BUNDLE_CLAIM_TYPE,
|
||||
{user_id: {device_id: {"room_id": room_id}}},
|
||||
)
|
||||
|
||||
@@ -32,7 +32,10 @@ from synapse.api.constants import EventTypes
|
||||
from synapse.api.errors import SynapseError
|
||||
from synapse.config._base import RootConfig
|
||||
from synapse.config.auto_accept_invites import AutoAcceptInvitesConfig
|
||||
from synapse.events.auto_accept_invites import InviteAutoAccepter
|
||||
from synapse.events.auto_accept_invites import (
|
||||
UNSTABLE_KEY_BUNDLE_CLAIM_TYPE,
|
||||
InviteAutoAccepter,
|
||||
)
|
||||
from synapse.handlers.sync import JoinedSyncResult, SyncRequestKey
|
||||
from synapse.module_api import ModuleApi
|
||||
from synapse.rest import admin
|
||||
@@ -123,6 +126,27 @@ class AutoAcceptInvitesTestCase(FederatingHomeserverTestCase):
|
||||
self.assertEqual(len(join_updates), 1)
|
||||
self.assertEqual(join_updates[0].room_id, room_id)
|
||||
|
||||
# The join was server-initiated, so the knocker's device should have
|
||||
# been designated (via a to-device message) to eagerly claim any
|
||||
# MSC4268 room key bundle for the room.
|
||||
devices = self.get_success(
|
||||
self.hs.get_datastores().main.get_devices_by_user(knocker_id)
|
||||
)
|
||||
self.assertEqual(len(devices), 1, devices)
|
||||
device_id = next(iter(devices))
|
||||
to_stream_id = self.hs.get_datastores().main.get_to_device_stream_token()
|
||||
messages, _ = self.get_success(
|
||||
self.hs.get_datastores().main.get_messages_for_device(
|
||||
knocker_id, device_id, 0, to_stream_id
|
||||
)
|
||||
)
|
||||
claim_messages = [
|
||||
m for m in messages if m["type"] == UNSTABLE_KEY_BUNDLE_CLAIM_TYPE
|
||||
]
|
||||
self.assertEqual(len(claim_messages), 1, messages)
|
||||
self.assertEqual(claim_messages[0]["content"], {"room_id": room_id})
|
||||
self.assertEqual(claim_messages[0]["sender"], knocker_id)
|
||||
|
||||
def test_no_auto_join_on_accepted_knock_by_default(self) -> None:
|
||||
"""With `enabled_for_accepted_knocks` off (the default), an accepted
|
||||
knock stays an ordinary invite."""
|
||||
|
||||
Reference in New Issue
Block a user