summary refs log tree commit diff
path: root/tests
diff options
context:
space:
mode:
authorDirk Klimpel <5740567+dklimpel@users.noreply.github.com>2020-07-14 13:36:23 +0200
committerGitHub <noreply@github.com>2020-07-14 12:36:23 +0100
commit491f0dab1ba5456f52b0710461fbaabc594ff1f5 (patch)
treee3ff2e5bf5515f45f880946d2f354351c240391b /tests
parentAdd the option to validate the `iss` and `aud` claims for JWT logins. (#7827) (diff)
downloadsynapse-491f0dab1ba5456f52b0710461fbaabc594ff1f5.tar.xz
Add delete room admin endpoint (#7613)
The Delete Room admin API allows server admins to remove rooms from server
and block these rooms.
`DELETE /_synapse/admin/v1/rooms/<room_id>`
It is a combination and improvement of "[Shutdown room](https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/shutdown_room.md)" and "[Purge room](https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/purge_room.md)" API.

Fixes: #6425 

It also fixes a bug in [synapse/storage/data_stores/main/room.py](synapse/storage/data_stores/main/room.py) in ` get_room_with_stats`.
It should return `None` if the room is unknown. But it returns an `IndexError`.
https://github.com/matrix-org/synapse/blob/901b1fa561e3cc661d78aa96d59802cf2078cb0d/synapse/storage/data_stores/main/room.py#L99-L105

Related to:
- #5575
- https://github.com/Awesome-Technologies/synapse-admin/issues/17

Signed-off-by: Dirk Klimpel dirk@klimpel.org
Diffstat (limited to 'tests')
-rw-r--r--tests/rest/admin/test_room.py395
-rw-r--r--tests/storage/test_room.py8
2 files changed, 403 insertions, 0 deletions
diff --git a/tests/rest/admin/test_room.py b/tests/rest/admin/test_room.py
index ae6d05a043..a80537c4fc 100644
--- a/tests/rest/admin/test_room.py
+++ b/tests/rest/admin/test_room.py
@@ -151,6 +151,401 @@ class ShutdownRoomTestCase(unittest.HomeserverTestCase):
         )

 

 

+class DeleteRoomTestCase(unittest.HomeserverTestCase):

+    servlets = [

+        synapse.rest.admin.register_servlets,

+        login.register_servlets,

+        events.register_servlets,

+        room.register_servlets,

+        room.register_deprecated_servlets,

+    ]

+

+    def prepare(self, reactor, clock, hs):

+        self.event_creation_handler = hs.get_event_creation_handler()

+        hs.config.user_consent_version = "1"

+

+        consent_uri_builder = Mock()

+        consent_uri_builder.build_user_consent_uri.return_value = "http://example.com"

+        self.event_creation_handler._consent_uri_builder = consent_uri_builder

+

+        self.store = hs.get_datastore()

+

+        self.admin_user = self.register_user("admin", "pass", admin=True)

+        self.admin_user_tok = self.login("admin", "pass")

+

+        self.other_user = self.register_user("user", "pass")

+        self.other_user_tok = self.login("user", "pass")

+

+        # Mark the admin user as having consented

+        self.get_success(self.store.user_set_consent_version(self.admin_user, "1"))

+

+        self.room_id = self.helper.create_room_as(

+            self.other_user, tok=self.other_user_tok

+        )

+        self.url = "/_synapse/admin/v1/rooms/%s/delete" % self.room_id

+

+    def test_requester_is_no_admin(self):

+        """

+        If the user is not a server admin, an error 403 is returned.

+        """

+

+        request, channel = self.make_request(

+            "POST", self.url, json.dumps({}), access_token=self.other_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])

+

+    def test_room_does_not_exist(self):

+        """

+        Check that unknown rooms/server return error 404.

+        """

+        url = "/_synapse/admin/v1/rooms/!unknown:test/delete"

+

+        request, channel = self.make_request(

+            "POST", url, json.dumps({}), access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])

+

+    def test_room_is_not_valid(self):

+        """

+        Check that invalid room names, return an error 400.

+        """

+        url = "/_synapse/admin/v1/rooms/invalidroom/delete"

+

+        request, channel = self.make_request(

+            "POST", url, json.dumps({}), access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(

+            "invalidroom is not a legal room ID", channel.json_body["error"],

+        )

+

+    def test_new_room_user_does_not_exist(self):

+        """

+        Tests that the user ID must be from local server but it does not have to exist.

+        """

+        body = json.dumps({"new_room_user_id": "@unknown:test"})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertIn("new_room_id", channel.json_body)

+        self.assertIn("kicked_users", channel.json_body)

+        self.assertIn("failed_to_kick_users", channel.json_body)

+        self.assertIn("local_aliases", channel.json_body)

+

+    def test_new_room_user_is_not_local(self):

+        """

+        Check that only local users can create new room to move members.

+        """

+        body = json.dumps({"new_room_user_id": "@not:exist.bla"})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(

+            "User must be our own: @not:exist.bla", channel.json_body["error"],

+        )

+

+    def test_block_is_not_bool(self):

+        """

+        If parameter `block` is not boolean, return an error

+        """

+        body = json.dumps({"block": "NotBool"})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url,

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(Codes.BAD_JSON, channel.json_body["errcode"])

+

+    def test_purge_room_and_block(self):

+        """Test to purge a room and block it.

+        Members will not be moved to a new room and will not receive a message.

+        """

+        # Test that room is not purged

+        with self.assertRaises(AssertionError):

+            self._is_purged(self.room_id)

+

+        # Test that room is not blocked

+        self._is_blocked(self.room_id, expect=False)

+

+        # Assert one user in room

+        self._is_member(room_id=self.room_id, user_id=self.other_user)

+

+        body = json.dumps({"block": True})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url.encode("ascii"),

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(None, channel.json_body["new_room_id"])

+        self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])

+        self.assertIn("failed_to_kick_users", channel.json_body)

+        self.assertIn("local_aliases", channel.json_body)

+

+        self._is_purged(self.room_id)

+        self._is_blocked(self.room_id, expect=True)

+        self._has_no_members(self.room_id)

+

+    def test_purge_room_and_not_block(self):

+        """Test to purge a room and do not block it.

+        Members will not be moved to a new room and will not receive a message.

+        """

+        # Test that room is not purged

+        with self.assertRaises(AssertionError):

+            self._is_purged(self.room_id)

+

+        # Test that room is not blocked

+        self._is_blocked(self.room_id, expect=False)

+

+        # Assert one user in room

+        self._is_member(room_id=self.room_id, user_id=self.other_user)

+

+        body = json.dumps({"block": False})

+

+        request, channel = self.make_request(

+            "POST",

+            self.url.encode("ascii"),

+            content=body.encode(encoding="utf_8"),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(None, channel.json_body["new_room_id"])

+        self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])

+        self.assertIn("failed_to_kick_users", channel.json_body)

+        self.assertIn("local_aliases", channel.json_body)

+

+        self._is_purged(self.room_id)

+        self._is_blocked(self.room_id, expect=False)

+        self._has_no_members(self.room_id)

+

+    def test_shutdown_room_consent(self):

+        """Test that we can shutdown rooms with local users who have not

+        yet accepted the privacy policy. This used to fail when we tried to

+        force part the user from the old room.

+        Members will be moved to a new room and will receive a message.

+        """

+        self.event_creation_handler._block_events_without_consent_error = None

+

+        # Assert one user in room

+        users_in_room = self.get_success(self.store.get_users_in_room(self.room_id))

+        self.assertEqual([self.other_user], users_in_room)

+

+        # Enable require consent to send events

+        self.event_creation_handler._block_events_without_consent_error = "Error"

+

+        # Assert that the user is getting consent error

+        self.helper.send(

+            self.room_id, body="foo", tok=self.other_user_tok, expect_code=403

+        )

+

+        # Test that room is not purged

+        with self.assertRaises(AssertionError):

+            self._is_purged(self.room_id)

+

+        # Assert one user in room

+        self._is_member(room_id=self.room_id, user_id=self.other_user)

+

+        # Test that the admin can still send shutdown

+        url = "/_synapse/admin/v1/rooms/%s/delete" % self.room_id

+        request, channel = self.make_request(

+            "POST",

+            url.encode("ascii"),

+            json.dumps({"new_room_user_id": self.admin_user}),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])

+        self.assertIn("new_room_id", channel.json_body)

+        self.assertIn("failed_to_kick_users", channel.json_body)

+        self.assertIn("local_aliases", channel.json_body)

+

+        # Test that member has moved to new room

+        self._is_member(

+            room_id=channel.json_body["new_room_id"], user_id=self.other_user

+        )

+

+        self._is_purged(self.room_id)

+        self._has_no_members(self.room_id)

+

+    def test_shutdown_room_block_peek(self):

+        """Test that a world_readable room can no longer be peeked into after

+        it has been shut down.

+        Members will be moved to a new room and will receive a message.

+        """

+        self.event_creation_handler._block_events_without_consent_error = None

+

+        # Enable world readable

+        url = "rooms/%s/state/m.room.history_visibility" % (self.room_id,)

+        request, channel = self.make_request(

+            "PUT",

+            url.encode("ascii"),

+            json.dumps({"history_visibility": "world_readable"}),

+            access_token=self.other_user_tok,

+        )

+        self.render(request)

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+

+        # Test that room is not purged

+        with self.assertRaises(AssertionError):

+            self._is_purged(self.room_id)

+

+        # Assert one user in room

+        self._is_member(room_id=self.room_id, user_id=self.other_user)

+

+        # Test that the admin can still send shutdown

+        url = "/_synapse/admin/v1/rooms/%s/delete" % self.room_id

+        request, channel = self.make_request(

+            "POST",

+            url.encode("ascii"),

+            json.dumps({"new_room_user_id": self.admin_user}),

+            access_token=self.admin_user_tok,

+        )

+        self.render(request)

+

+        self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])

+        self.assertEqual(self.other_user, channel.json_body["kicked_users"][0])

+        self.assertIn("new_room_id", channel.json_body)

+        self.assertIn("failed_to_kick_users", channel.json_body)

+        self.assertIn("local_aliases", channel.json_body)

+

+        # Test that member has moved to new room

+        self._is_member(

+            room_id=channel.json_body["new_room_id"], user_id=self.other_user

+        )

+

+        self._is_purged(self.room_id)

+        self._has_no_members(self.room_id)

+

+        # Assert we can no longer peek into the room

+        self._assert_peek(self.room_id, expect_code=403)

+

+    def _is_blocked(self, room_id, expect=True):

+        """Assert that the room is blocked or not

+        """

+        d = self.store.is_room_blocked(room_id)

+        if expect:

+            self.assertTrue(self.get_success(d))

+        else:

+            self.assertIsNone(self.get_success(d))

+

+    def _has_no_members(self, room_id):

+        """Assert there is now no longer anyone in the room

+        """

+        users_in_room = self.get_success(self.store.get_users_in_room(room_id))

+        self.assertEqual([], users_in_room)

+

+    def _is_member(self, room_id, user_id):

+        """Test that user is member of the room

+        """

+        users_in_room = self.get_success(self.store.get_users_in_room(room_id))

+        self.assertIn(user_id, users_in_room)

+

+    def _is_purged(self, room_id):

+        """Test that the following tables have been purged of all rows related to the room.

+        """

+        for table in (

+            "current_state_events",

+            "event_backward_extremities",

+            "event_forward_extremities",

+            "event_json",

+            "event_push_actions",

+            "event_search",

+            "events",

+            "group_rooms",

+            "public_room_list_stream",

+            "receipts_graph",

+            "receipts_linearized",

+            "room_aliases",

+            "room_depth",

+            "room_memberships",

+            "room_stats_state",

+            "room_stats_current",

+            "room_stats_historical",

+            "room_stats_earliest_token",

+            "rooms",

+            "stream_ordering_to_exterm",

+            "users_in_public_rooms",

+            "users_who_share_private_rooms",

+            "appservice_room_list",

+            "e2e_room_keys",

+            "event_push_summary",

+            "pusher_throttle",

+            "group_summary_rooms",

+            "local_invites",

+            "room_account_data",

+            "room_tags",

+            # "state_groups",  # Current impl leaves orphaned state groups around.

+            "state_groups_state",

+        ):

+            count = self.get_success(

+                self.store.db.simple_select_one_onecol(

+                    table=table,

+                    keyvalues={"room_id": room_id},

+                    retcol="COUNT(*)",

+                    desc="test_purge_room",

+                )

+            )

+

+            self.assertEqual(count, 0, msg="Rows not purged in {}".format(table))

+

+    def _assert_peek(self, room_id, expect_code):

+        """Assert that the admin user can (or cannot) peek into the room.

+        """

+

+        url = "rooms/%s/initialSync" % (room_id,)

+        request, channel = self.make_request(

+            "GET", url.encode("ascii"), access_token=self.admin_user_tok

+        )

+        self.render(request)

+        self.assertEqual(

+            expect_code, int(channel.result["code"]), msg=channel.result["body"]

+        )

+

+        url = "events?timeout=0&room_id=" + room_id

+        request, channel = self.make_request(

+            "GET", url.encode("ascii"), access_token=self.admin_user_tok

+        )

+        self.render(request)

+        self.assertEqual(

+            expect_code, int(channel.result["code"]), msg=channel.result["body"]

+        )

+

+

 class PurgeRoomTestCase(unittest.HomeserverTestCase):

     """Test /purge_room admin API.

     """

diff --git a/tests/storage/test_room.py b/tests/storage/test_room.py
index 3b78d48896..b1dceb2918 100644
--- a/tests/storage/test_room.py
+++ b/tests/storage/test_room.py
@@ -56,6 +56,10 @@ class RoomStoreTestCase(unittest.TestCase):
         )
 
     @defer.inlineCallbacks
+    def test_get_room_unknown_room(self):
+        self.assertIsNone((yield self.store.get_room("!uknown:test")),)
+
+    @defer.inlineCallbacks
     def test_get_room_with_stats(self):
         self.assertDictContainsSubset(
             {
@@ -66,6 +70,10 @@ class RoomStoreTestCase(unittest.TestCase):
             (yield self.store.get_room_with_stats(self.room.to_string())),
         )
 
+    @defer.inlineCallbacks
+    def test_get_room_with_stats_unknown_room(self):
+        self.assertIsNone((yield self.store.get_room_with_stats("!uknown:test")),)
+
 
 class RoomEventsStoreTestCase(unittest.TestCase):
     @defer.inlineCallbacks