mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-05-05 14:14:56 -04:00
Implement knock feature (#6739)
This PR aims to implement the knock feature as proposed in https://github.com/matrix-org/matrix-doc/pull/2403 Signed-off-by: Sorunome mail@sorunome.de Signed-off-by: Andrew Morgan andrewm@element.io
This commit is contained in:
parent
11846dff8c
commit
d936371b69
29 changed files with 1614 additions and 119 deletions
|
@ -1,6 +1,5 @@
|
|||
# Copyright 2014-2016 OpenMarket Ltd
|
||||
# Copyright 2017-2018 New Vector Ltd
|
||||
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
# Copyright 2014-2021 The Matrix.org Foundation C.I.C.
|
||||
# Copyright 2020 Sorunome
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -1550,6 +1549,77 @@ class FederationHandler(BaseHandler):
|
|||
|
||||
run_in_background(self._handle_queued_pdus, room_queue)
|
||||
|
||||
@log_function
|
||||
async def do_knock(
|
||||
self,
|
||||
target_hosts: List[str],
|
||||
room_id: str,
|
||||
knockee: str,
|
||||
content: JsonDict,
|
||||
) -> Tuple[str, int]:
|
||||
"""Sends the knock to the remote server.
|
||||
|
||||
This first triggers a make_knock request that returns a partial
|
||||
event that we can fill out and sign. This is then sent to the
|
||||
remote server via send_knock.
|
||||
|
||||
Knock events must be signed by the knockee's server before distributing.
|
||||
|
||||
Args:
|
||||
target_hosts: A list of hosts that we want to try knocking through.
|
||||
room_id: The ID of the room to knock on.
|
||||
knockee: The ID of the user who is knocking.
|
||||
content: The content of the knock event.
|
||||
|
||||
Returns:
|
||||
A tuple of (event ID, stream ID).
|
||||
|
||||
Raises:
|
||||
SynapseError: If the chosen remote server returns a 3xx/4xx code.
|
||||
RuntimeError: If no servers were reachable.
|
||||
"""
|
||||
logger.debug("Knocking on room %s on behalf of user %s", room_id, knockee)
|
||||
|
||||
# Inform the remote server of the room versions we support
|
||||
supported_room_versions = list(KNOWN_ROOM_VERSIONS.keys())
|
||||
|
||||
# Ask the remote server to create a valid knock event for us. Once received,
|
||||
# we sign the event
|
||||
params = {"ver": supported_room_versions} # type: Dict[str, Iterable[str]]
|
||||
origin, event, event_format_version = await self._make_and_verify_event(
|
||||
target_hosts, room_id, knockee, Membership.KNOCK, content, params=params
|
||||
)
|
||||
|
||||
# Record the room ID and its version so that we have a record of the room
|
||||
await self._maybe_store_room_on_outlier_membership(
|
||||
room_id=event.room_id, room_version=event_format_version
|
||||
)
|
||||
|
||||
# Initially try the host that we successfully called /make_knock on
|
||||
try:
|
||||
target_hosts.remove(origin)
|
||||
target_hosts.insert(0, origin)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Send the signed event back to the room, and potentially receive some
|
||||
# further information about the room in the form of partial state events
|
||||
stripped_room_state = await self.federation_client.send_knock(
|
||||
target_hosts, event
|
||||
)
|
||||
|
||||
# Store any stripped room state events in the "unsigned" key of the event.
|
||||
# This is a bit of a hack and is cribbing off of invites. Basically we
|
||||
# store the room state here and retrieve it again when this event appears
|
||||
# in the invitee's sync stream. It is stripped out for all other local users.
|
||||
event.unsigned["knock_room_state"] = stripped_room_state["knock_state_events"]
|
||||
|
||||
context = await self.state_handler.compute_event_context(event)
|
||||
stream_id = await self.persist_events_and_notify(
|
||||
event.room_id, [(event, context)]
|
||||
)
|
||||
return event.event_id, stream_id
|
||||
|
||||
async def _handle_queued_pdus(
|
||||
self, room_queue: List[Tuple[EventBase, str]]
|
||||
) -> None:
|
||||
|
@ -1915,6 +1985,116 @@ class FederationHandler(BaseHandler):
|
|||
|
||||
return None
|
||||
|
||||
@log_function
|
||||
async def on_make_knock_request(
|
||||
self, origin: str, room_id: str, user_id: str
|
||||
) -> EventBase:
|
||||
"""We've received a make_knock request, so we create a partial
|
||||
knock event for the room and return that. We do *not* persist or
|
||||
process it until the other server has signed it and sent it back.
|
||||
|
||||
Args:
|
||||
origin: The (verified) server name of the requesting server.
|
||||
room_id: The room to create the knock event in.
|
||||
user_id: The user to create the knock for.
|
||||
|
||||
Returns:
|
||||
The partial knock event.
|
||||
"""
|
||||
if get_domain_from_id(user_id) != origin:
|
||||
logger.info(
|
||||
"Get /xyz.amorgan.knock/make_knock request for user %r"
|
||||
"from different origin %s, ignoring",
|
||||
user_id,
|
||||
origin,
|
||||
)
|
||||
raise SynapseError(403, "User not from origin", Codes.FORBIDDEN)
|
||||
|
||||
room_version = await self.store.get_room_version_id(room_id)
|
||||
|
||||
builder = self.event_builder_factory.new(
|
||||
room_version,
|
||||
{
|
||||
"type": EventTypes.Member,
|
||||
"content": {"membership": Membership.KNOCK},
|
||||
"room_id": room_id,
|
||||
"sender": user_id,
|
||||
"state_key": user_id,
|
||||
},
|
||||
)
|
||||
|
||||
event, context = await self.event_creation_handler.create_new_client_event(
|
||||
builder=builder
|
||||
)
|
||||
|
||||
event_allowed = await self.third_party_event_rules.check_event_allowed(
|
||||
event, context
|
||||
)
|
||||
if not event_allowed:
|
||||
logger.warning("Creation of knock %s forbidden by third-party rules", event)
|
||||
raise SynapseError(
|
||||
403, "This event is not allowed in this context", Codes.FORBIDDEN
|
||||
)
|
||||
|
||||
try:
|
||||
# The remote hasn't signed it yet, obviously. We'll do the full checks
|
||||
# when we get the event back in `on_send_knock_request`
|
||||
await self.auth.check_from_context(
|
||||
room_version, event, context, do_sig_check=False
|
||||
)
|
||||
except AuthError as e:
|
||||
logger.warning("Failed to create new knock %r because %s", event, e)
|
||||
raise e
|
||||
|
||||
return event
|
||||
|
||||
@log_function
|
||||
async def on_send_knock_request(
|
||||
self, origin: str, event: EventBase
|
||||
) -> EventContext:
|
||||
"""
|
||||
We have received a knock event for a room. Verify that event and send it into the room
|
||||
on the knocking homeserver's behalf.
|
||||
|
||||
Args:
|
||||
origin: The remote homeserver of the knocking user.
|
||||
event: The knocking member event that has been signed by the remote homeserver.
|
||||
|
||||
Returns:
|
||||
The context of the event after inserting it into the room graph.
|
||||
"""
|
||||
logger.debug(
|
||||
"on_send_knock_request: Got event: %s, signatures: %s",
|
||||
event.event_id,
|
||||
event.signatures,
|
||||
)
|
||||
|
||||
if get_domain_from_id(event.sender) != origin:
|
||||
logger.info(
|
||||
"Got /xyz.amorgan.knock/send_knock request for user %r "
|
||||
"from different origin %s",
|
||||
event.sender,
|
||||
origin,
|
||||
)
|
||||
raise SynapseError(403, "User not from origin", Codes.FORBIDDEN)
|
||||
|
||||
event.internal_metadata.outlier = False
|
||||
|
||||
context = await self.state_handler.compute_event_context(event)
|
||||
|
||||
await self._auth_and_persist_event(origin, event, context)
|
||||
|
||||
event_allowed = await self.third_party_event_rules.check_event_allowed(
|
||||
event, context
|
||||
)
|
||||
if not event_allowed:
|
||||
logger.info("Sending of knock %s forbidden by third-party rules", event)
|
||||
raise SynapseError(
|
||||
403, "This event is not allowed in this context", Codes.FORBIDDEN
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
async def get_state_for_pdu(self, room_id: str, event_id: str) -> List[EventBase]:
|
||||
"""Returns the state at the event. i.e. not including said event."""
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue