summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
authorAndrew Morgan <andrew@amorgan.xyz>2020-11-12 18:35:13 +0000
committerAndrew Morgan <andrew@amorgan.xyz>2020-11-13 16:23:00 +0000
commita2ab56a06868bd305fda5e97b2d478db5d7c73a5 (patch)
treed5923304303638f708e57eb847676cdfeb958941 /synapse
parentGeneralise _locally_reject_invite (diff)
downloadsynapse-a2ab56a06868bd305fda5e97b2d478db5d7c73a5.tar.xz
Implement locally rescinding a federated knock
As mentioned in the MSC, a user can rescind (take back) a knock while it
is pending by sending a leave event to the room. This will set their
membership to leave instead of knock.

Now, this functionality already worked before this commit for rooms that
the homeserver was already in. What didn't work was:

* Rescinding a knock over federation to a room with active homeservers
* Rescinding a knock over federation to a room with inactive homeservers

This commit addresses the second bullet point, and leaves the first
bullet point as a TODO (as it is an edge case an not immediately obvious
how it would be done).

What this commit does is crib off the same functionality as locally
rejecting an invite. That occurs when we are unable to contact the
homeserver that originally sent us an invite. Instead an out-of-band
leave membership event will be generated and sent to clients locally.

The same is happening here. You can mostly ignore the new
generate_local_out_of_band_membership methods, those are just some
structural bits to allow us to call that method from RoomMemberHandler.

The real meat of this commit is moving about and adding some logic in
`update_membership_locked`, specifically for when we're updating a
user's membership to "leave". There was already some code in there to
check whether the room to send the leave to was a room the homeserver is
not currently a part of. In that case, we'd remote reject the knock.
This commit just extends that to also rescind knocks if the user's
membership in the room is currently "knock". We skip the remote attempt
for now and go straight to generating a local leave membership event.
Diffstat (limited to 'synapse')
-rw-r--r--synapse/handlers/room_member.py97
-rw-r--r--synapse/handlers/room_member_worker.py31
2 files changed, 100 insertions, 28 deletions
diff --git a/synapse/handlers/room_member.py b/synapse/handlers/room_member.py
index ead214c813..621d114884 100644
--- a/synapse/handlers/room_member.py
+++ b/synapse/handlers/room_member.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 # Copyright 2016-2020 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.
@@ -148,6 +149,28 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
         raise NotImplementedError()
 
     @abc.abstractmethod
+    async def generate_local_out_of_band_membership(
+        self,
+        previous_membership_event: EventBase,
+        txn_id: Optional[str],
+        requester: Requester,
+        content: JsonDict,
+    ):
+        """Generate a local leave event for a room
+
+        Args:
+            previous_membership_event: the previous membership event for this user
+            txn_id: optional transaction ID supplied by the client
+            requester: user making the request, according to the access token
+            content: additional content to include in the leave event.
+               Normally an empty dict.
+
+        Returns:
+            A tuple containing (event_id, stream_id of the leave event)
+        """
+        raise NotImplementedError()
+
+    @abc.abstractmethod
     async def _user_left_room(self, target: UserID, room_id: str) -> None:
         """Notifies distributor on master process that the user has left the
         room.
@@ -532,39 +555,59 @@ class RoomMemberHandler(metaclass=abc.ABCMeta):
                 invite = await self.store.get_invite_for_local_user_in_room(
                     user_id=target.to_string(), room_id=room_id
                 )  # type: Optional[RoomsForUser]
-                if not invite:
+                if invite:
                     logger.info(
-                        "%s sent a leave request to %s, but that is not an active room "
-                        "on this server, and there is no pending invite",
+                        "%s rejects invite to %s from %s",
                         target,
                         room_id,
+                        invite.sender,
                     )
 
-                    raise SynapseError(404, "Not a known room")
+                    if not self.hs.is_mine_id(invite.sender):
+                        # send the rejection to the inviter's HS (with fallback to
+                        # local event)
+                        return await self.remote_reject_invite(
+                            invite.event_id, txn_id, requester, content,
+                        )
+
+                    # the inviter was on our server, but has now left. Carry on
+                    # with the normal rejection codepath, which will also send the
+                    # rejection out to any other servers we believe are still in the room.
+
+                    # thanks to overzealous cleaning up of event_forward_extremities in
+                    # `delete_old_current_state_events`, it's possible to end up with no
+                    # forward extremities here. If that happens, let's just hang the
+                    # rejection off the invite event.
+                    #
+                    # see: https://github.com/matrix-org/synapse/issues/7139
+                    if len(latest_event_ids) == 0:
+                        latest_event_ids = [invite.event_id]
+
+                else:
+                    # or perhaps this is a remote room that a local user has knocked on
+                    knock = await self.store.get_knock_for_local_user_in_room(
+                        user_id=target.to_string(), room_id=room_id
+                    )  # type: Optional[RoomsForUser]
+                    if knock:
+                        # TODO: We don't yet support rescinding knocks over federation
+                        # as we don't know which homeserver to send it to. An obvious
+                        # candidate is the remote homeserver we originally knocked through,
+                        # however we don't currently store that information.
+
+                        # Just rescind the knock locally
+                        knock_event = await self.store.get_event(knock.event_id)
+                        return await self.generate_local_out_of_band_membership(
+                            knock_event, txn_id, requester, content
+                        )
 
-                logger.info(
-                    "%s rejects invite to %s from %s", target, room_id, invite.sender
-                )
-
-                if not self.hs.is_mine_id(invite.sender):
-                    # send the rejection to the inviter's HS (with fallback to
-                    # local event)
-                    return await self.remote_reject_invite(
-                        invite.event_id, txn_id, requester, content,
+                    logger.info(
+                        "%s sent a leave request to %s, but that is not an active room "
+                        "on this server, and there is no pending knock",
+                        target,
+                        room_id,
                     )
 
-                # the inviter was on our server, but has now left. Carry on
-                # with the normal rejection codepath, which will also send the
-                # rejection out to any other servers we believe are still in the room.
-
-                # thanks to overzealous cleaning up of event_forward_extremities in
-                # `delete_old_current_state_events`, it's possible to end up with no
-                # forward extremities here. If that happens, let's just hang the
-                # rejection off the invite event.
-                #
-                # see: https://github.com/matrix-org/synapse/issues/7139
-                if len(latest_event_ids) == 0:
-                    latest_event_ids = [invite.event_id]
+                    raise SynapseError(404, "Not a known room")
 
         elif effective_membership_state == Membership.KNOCK:
             if not is_host_in_room:
@@ -1135,11 +1178,11 @@ class RoomMemberMasterHandler(RoomMemberHandler):
             #
             logger.warning("Failed to reject invite: %s", e)
 
-            return await self._generate_local_out_of_band_membership(
+            return await self.generate_local_out_of_band_membership(
                 invite_event, txn_id, requester, content
             )
 
-    async def _generate_local_out_of_band_membership(
+    async def generate_local_out_of_band_membership(
         self,
         previous_membership_event: EventBase,
         txn_id: Optional[str],
diff --git a/synapse/handlers/room_member_worker.py b/synapse/handlers/room_member_worker.py
index f9cea604ec..e72c0f40b3 100644
--- a/synapse/handlers/room_member_worker.py
+++ b/synapse/handlers/room_member_worker.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 # Copyright 2018 New Vector Ltd
+# Copyright 2020 The Matrix.org Foundation C.I.C.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,13 +18,14 @@ import logging
 from typing import List, Optional, Tuple
 
 from synapse.api.errors import SynapseError
+from synapse.events import EventBase
 from synapse.handlers.room_member import RoomMemberHandler
 from synapse.replication.http.membership import (
     ReplicationRemoteJoinRestServlet as ReplRemoteJoin,
     ReplicationRemoteRejectInviteRestServlet as ReplRejectInvite,
     ReplicationUserJoinedLeftRoomRestServlet as ReplJoinedLeft,
 )
-from synapse.types import Requester, UserID
+from synapse.types import JsonDict, Requester, UserID
 
 logger = logging.getLogger(__name__)
 
@@ -79,6 +81,33 @@ class RoomMemberWorkerHandler(RoomMemberHandler):
         )
         return ret["event_id"], ret["stream_id"]
 
+    async def generate_local_out_of_band_membership(
+        self,
+        previous_membership_event: EventBase,
+        txn_id: Optional[str],
+        requester: Requester,
+        content: JsonDict,
+    ):
+        """Generate a local leave event for a room
+
+        Args:
+            previous_membership_event: the previous membership event for this user
+            txn_id: optional transaction ID supplied by the client
+            requester: user making the request, according to the access token
+            content: additional content to include in the leave event.
+               Normally an empty dict.
+
+        Returns:
+            A tuple containing (event_id, stream_id of the leave event)
+        """
+        ret = await self.generate_local_out_of_band_membership(
+            previous_membership_event=previous_membership_event,
+            txn_id=txn_id,
+            requester=requester,
+            content=content,
+        )
+        return ret["event_id"], ret["stream_id"]
+
     async def _remote_knock(
         self, remote_room_hosts: List[str], room_id: str, user: UserID, content: dict,
     ) -> Tuple[str, int]: