From d7d15dce9c73d29ab2d5e182b6d2eafca57d2455 Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Sun, 24 Oct 2021 13:41:04 -0300 Subject: [PATCH 1/9] Add knock information in admin exported data Signed-off-by: Rafael Goncalves --- synapse/handlers/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse/handlers/admin.py b/synapse/handlers/admin.py index a53cd62d3ca1..c66b8c66e973 100644 --- a/synapse/handlers/admin.py +++ b/synapse/handlers/admin.py @@ -90,6 +90,7 @@ async def export_user_data(self, user_id: str, writer: "ExfiltrationWriter") -> Membership.LEAVE, Membership.BAN, Membership.INVITE, + Membership.KNOCK, ), ) From 4615647965ae7080e0b1125851ede8c814f2d7d3 Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Sun, 24 Oct 2021 16:13:21 -0300 Subject: [PATCH 2/9] Add changelog information --- changelog.d/11171.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/11171.misc diff --git a/changelog.d/11171.misc b/changelog.d/11171.misc new file mode 100644 index 000000000000..b6a41a96da7f --- /dev/null +++ b/changelog.d/11171.misc @@ -0,0 +1 @@ +Add knock information in admin export. Contributed by Rafael Gonçalves. From 37361bfcdb447f7e5d0099a3668b85d1427cdf81 Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Wed, 27 Oct 2021 15:29:18 -0300 Subject: [PATCH 3/9] Create function to write knock data Signed-off-by: Rafael Goncalves --- synapse/app/admin_cmd.py | 12 ++++++++++++ synapse/handlers/admin.py | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/synapse/app/admin_cmd.py b/synapse/app/admin_cmd.py index 2fc848596d61..f0ed09c71946 100644 --- a/synapse/app/admin_cmd.py +++ b/synapse/app/admin_cmd.py @@ -145,6 +145,18 @@ def write_invite(self, room_id, event, state): for event in state.values(): print(json.dumps(event), file=f) + def write_knock(self, room_id, event, state): + self.write_events(room_id, [event]) + + room_directory = os.path.join(self.base_directory, "rooms", room_id) + os.makedirs(room_directory, exist_ok=True) + + knock_state = os.path.join(room_directory, "knock_state") + + with open(knock_state, "a") as f: + for event in state.values(): + print(json.dumps(event), file=f) + def finished(self): return self.base_directory diff --git a/synapse/handlers/admin.py b/synapse/handlers/admin.py index c66b8c66e973..e6df97204910 100644 --- a/synapse/handlers/admin.py +++ b/synapse/handlers/admin.py @@ -123,8 +123,16 @@ async def export_user_data(self, user_id: str, writer: "ExfiltrationWriter") -> invited_state = invite.unsigned["invite_room_state"] writer.write_invite(room_id, invite, invited_state) + if room.membership == Membership.KNOCK: + event_id = room.event_id + knock = await self.store.get_event(event_id, allow_none=True) + if knock: + knock_state = knock.unsigned["knock_room_state"] + writer.write_knock(room_id, knock, knock_state) + continue + # We only want to bother fetching events up to the last time they # were joined. We estimate that point by looking at the # stream_ordering of the last membership if it wasn't a join. From bbbc5b41cbffac686e34e8b7be3b7c784103cb8d Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Wed, 27 Oct 2021 15:30:38 -0300 Subject: [PATCH 4/9] Create knock data export test Signed-off-by: Rafael Goncalves --- tests/handlers/test_admin.py | 35 +++++++++++++++++++++++++++++++++-- tests/rest/client/utils.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/tests/handlers/test_admin.py b/tests/handlers/test_admin.py index 59de1142b157..e244f7847212 100644 --- a/tests/handlers/test_admin.py +++ b/tests/handlers/test_admin.py @@ -17,8 +17,9 @@ import synapse.rest.admin import synapse.storage -from synapse.api.constants import EventTypes -from synapse.rest.client import login, room +from synapse.api.constants import EventTypes, JoinRules +from synapse.api.room_versions import RoomVersions +from synapse.rest.client import login, room, knock from tests import unittest @@ -28,6 +29,7 @@ class ExfiltrateData(unittest.HomeserverTestCase): synapse.rest.admin.register_servlets_for_client_rest_resource, login.register_servlets, room.register_servlets, + knock.register_servlets, ] def prepare(self, reactor, clock, hs): @@ -201,3 +203,32 @@ def test_invite(self): self.assertEqual(args[0], room_id) self.assertEqual(args[1].content["membership"], "invite") self.assertTrue(args[2]) # Assert there is at least one bit of state + + def test_knock(self): + """Tests that knock get handled correctly.""" + # create a knockable v7 room + room_id = self.helper.create_room_as( + self.user1, room_version=RoomVersions.V7.identifier, tok=self.token1 + ) + self.helper.send_state( + room_id, + EventTypes.JoinRules, + {"join_rule": JoinRules.KNOCK}, + tok=self.token1, + ) + + self.helper.send(room_id, body="Hello!", tok=self.token1) + self.helper.knock(room_id, self.user2, tok=self.token2) + + writer = Mock() + + self.get_success(self.admin_handler.export_user_data(self.user2, writer)) + + writer.write_events.assert_not_called() + writer.write_state.assert_not_called() + writer.write_knock.assert_called_once() + + args = writer.write_knock.call_args[0] + self.assertEqual(args[0], room_id) + self.assertEqual(args[1].content["membership"], "knock") + self.assertTrue(args[2]) diff --git a/tests/rest/client/utils.py b/tests/rest/client/utils.py index 71fa87ce9291..7986170e88b5 100644 --- a/tests/rest/client/utils.py +++ b/tests/rest/client/utils.py @@ -120,6 +120,35 @@ def join(self, room=None, user=None, expect_code=200, tok=None): expect_code=expect_code, ) + + def knock(self, room=None, user=None, expect_code=200, tok=None): + temp_id = self.auth_user_id + self.auth_user_id = user + path = "/knock/%s" % room + if tok: + path = path + "?access_token=%s" % tok + + data = {"membership": "knock"} + + channel = make_request( + self.hs.get_reactor(), + self.site, + "POST", + path, + json.dumps(data).encode("utf8"), + ) + + assert ( + int(channel.result["code"]) == expect_code + ), "Expected: %d, got: %d, resp: %r" % ( + expect_code, + int(channel.result["code"]), + channel.result["body"], + ) + + self.auth_user_id = temp_id + + def leave(self, room=None, user=None, expect_code=200, tok=None): self.change_membership( room=room, From b116fb33ebe0e8ef734ef9a66a0504424c2594e5 Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Thu, 28 Oct 2021 10:52:23 -0300 Subject: [PATCH 5/9] Add comments Signed-off-by: Rafael Goncalves --- synapse/app/admin_cmd.py | 2 ++ tests/handlers/test_admin.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/synapse/app/admin_cmd.py b/synapse/app/admin_cmd.py index f0ed09c71946..ad20b1d6aa99 100644 --- a/synapse/app/admin_cmd.py +++ b/synapse/app/admin_cmd.py @@ -148,6 +148,8 @@ def write_invite(self, room_id, event, state): def write_knock(self, room_id, event, state): self.write_events(room_id, [event]) + # We write the knock state somewhere else as they aren't full events + # and are only a subset of the state at the event. room_directory = os.path.join(self.base_directory, "rooms", room_id) os.makedirs(room_directory, exist_ok=True) diff --git a/tests/handlers/test_admin.py b/tests/handlers/test_admin.py index e244f7847212..069b0ca0ec35 100644 --- a/tests/handlers/test_admin.py +++ b/tests/handlers/test_admin.py @@ -231,4 +231,4 @@ def test_knock(self): args = writer.write_knock.call_args[0] self.assertEqual(args[0], room_id) self.assertEqual(args[1].content["membership"], "knock") - self.assertTrue(args[2]) + self.assertTrue(args[2]) # Assert there is at least one bit of state From 58b5a2994bcc64f7980c85e76f1e482ac8d1a3aa Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Thu, 28 Oct 2021 10:53:24 -0300 Subject: [PATCH 6/9] Remove extra blank line Signed-off-by: Rafael Goncalves --- synapse/handlers/admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/handlers/admin.py b/synapse/handlers/admin.py index e6df97204910..25ddcc313566 100644 --- a/synapse/handlers/admin.py +++ b/synapse/handlers/admin.py @@ -132,7 +132,6 @@ async def export_user_data(self, user_id: str, writer: "ExfiltrationWriter") -> continue - # We only want to bother fetching events up to the last time they # were joined. We estimate that point by looking at the # stream_ordering of the last membership if it wasn't a join. From 060706dc8b919cac32fbb49605f9e1ed6764aebb Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Thu, 28 Oct 2021 11:09:42 -0300 Subject: [PATCH 7/9] Correct import order Signed-off-by: Rafael Goncalves --- tests/handlers/test_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/handlers/test_admin.py b/tests/handlers/test_admin.py index 069b0ca0ec35..abf2a0fe0dc5 100644 --- a/tests/handlers/test_admin.py +++ b/tests/handlers/test_admin.py @@ -19,7 +19,7 @@ import synapse.storage from synapse.api.constants import EventTypes, JoinRules from synapse.api.room_versions import RoomVersions -from synapse.rest.client import login, room, knock +from synapse.rest.client import knock, login, room from tests import unittest From 3848df7b4432c91b94f325da4d239bcfc7eabc78 Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Thu, 28 Oct 2021 11:10:22 -0300 Subject: [PATCH 8/9] Remove incorrect membership field and add reason field in body of knock request Signed-off-by: Rafael Goncalves --- tests/rest/client/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rest/client/utils.py b/tests/rest/client/utils.py index 7986170e88b5..ec0979850b20 100644 --- a/tests/rest/client/utils.py +++ b/tests/rest/client/utils.py @@ -120,15 +120,16 @@ def join(self, room=None, user=None, expect_code=200, tok=None): expect_code=expect_code, ) - - def knock(self, room=None, user=None, expect_code=200, tok=None): + def knock(self, room=None, user=None, reason=None, expect_code=200, tok=None): temp_id = self.auth_user_id self.auth_user_id = user path = "/knock/%s" % room if tok: path = path + "?access_token=%s" % tok - data = {"membership": "knock"} + data = {} + if reason: + data["reason"] = reason channel = make_request( self.hs.get_reactor(), @@ -148,7 +149,6 @@ def knock(self, room=None, user=None, expect_code=200, tok=None): self.auth_user_id = temp_id - def leave(self, room=None, user=None, expect_code=200, tok=None): self.change_membership( room=room, From b36077e07fdcd99882012d3921287f053e47d7e0 Mon Sep 17 00:00:00 2001 From: Rafael Goncalves Date: Thu, 28 Oct 2021 14:04:42 -0300 Subject: [PATCH 9/9] Create write_knock function in ExfiltrationWriter Signed-off-by: Rafael Goncalves --- synapse/handlers/admin.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/synapse/handlers/admin.py b/synapse/handlers/admin.py index 25ddcc313566..be3203ac807b 100644 --- a/synapse/handlers/admin.py +++ b/synapse/handlers/admin.py @@ -246,6 +246,20 @@ def write_invite( """ raise NotImplementedError() + @abc.abstractmethod + def write_knock( + self, room_id: str, event: EventBase, state: StateMap[dict] + ) -> None: + """Write a knock for the room, with associated knock state. + + Args: + room_id: The room ID the knock is for. + event: The knock event. + state: A subset of the state at the knock, with a subset of the + event keys (type, state_key content and sender). + """ + raise NotImplementedError() + @abc.abstractmethod def finished(self) -> Any: """Called when all data has successfully been exported and written.