summary refs log tree commit diff
diff options
context:
space:
mode:
authorTravis Ralston <travpc@gmail.com>2021-01-19 13:59:29 -0700
committerTravis Ralston <travpc@gmail.com>2021-01-19 13:59:29 -0700
commit40f96320a2eb0049cb5defaf52530b8e08095a14 (patch)
treea5192ffbc0f48de65b16313409ca5d197c3e79ac
parentValidate the server name for the /publicRooms endpoint. (#9161) (diff)
downloadsynapse-40f96320a2eb0049cb5defaf52530b8e08095a14.tar.xz
Add an admin API to get the current room state
This could arguably replace the existing admin API for `/members`, however that is out of scope of this change.

This sort of endpoint is ideal for moderation use cases as well as other applications, such as needing to retrieve various bits of information about a room to perform a task (like syncing power levels between two places). This endpoint exposes nothing more than an admin would be able to access with a `select *` query on their database.
-rw-r--r--docs/admin_api/rooms.md30
-rw-r--r--synapse/handlers/message.py13
-rw-r--r--synapse/rest/admin/__init__.py2
-rw-r--r--synapse/rest/admin/rooms.py32
-rw-r--r--tests/rest/admin/test_room.py15
5 files changed, 88 insertions, 4 deletions
diff --git a/docs/admin_api/rooms.md b/docs/admin_api/rooms.md
index 9e560003a9..d68003853b 100644
--- a/docs/admin_api/rooms.md
+++ b/docs/admin_api/rooms.md
@@ -367,6 +367,36 @@ Response:
 }
 ```
 
+# Room State API
+
+The Room State admin API allows server admins to get a list of all state events in a room.
+
+The response includes the following fields:
+
+* `state` - The current state of the room at the time of request.
+
+## Usage
+
+A standard request:
+
+```
+GET /_synapse/admin/v1/rooms/<room_id>/state
+
+{}
+```
+
+Response:
+
+```json
+{
+  "state": [
+    {"type": "m.room.create", "state_key": "", "etc": true},
+    {"type": "m.room.power_levels", "state_key": "", "etc": true},
+    {"type": "m.room.name", "state_key": "", "etc": true}
+  ]
+}
+```
+
 # Delete Room API
 
 The Delete Room admin API allows server admins to remove rooms from server
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 9dfeab09cd..457491b75d 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -137,6 +137,7 @@ class MessageHandler:
         state_filter: StateFilter = StateFilter.all(),
         at_token: Optional[StreamToken] = None,
         is_guest: bool = False,
+        is_admin: bool = False,
     ) -> List[dict]:
         """Retrieve all state events for a given room. If the user is
         joined to the room then return the current state. If the user has
@@ -153,6 +154,7 @@ class MessageHandler:
                 stream token, we raise a 403 SynapseError. If None, returns the current
                 state based on the current_state_events table.
             is_guest: whether this user is a guest
+            is_admin: whether this user is making the request as a server admin.
         Returns:
             A list of dicts representing state events. [{}, {}, {}]
         Raises:
@@ -173,9 +175,12 @@ class MessageHandler:
             if not last_events:
                 raise NotFoundError("Can't find event for token %s" % (at_token,))
 
-            visible_events = await filter_events_for_client(
-                self.storage, user_id, last_events, filter_send_to_client=False
-            )
+            if is_admin:
+                visible_events = last_events
+            else:
+                visible_events = await filter_events_for_client(
+                    self.storage, user_id, last_events, filter_send_to_client=False,
+                )
 
             event = last_events[0]
             if visible_events:
@@ -197,7 +202,7 @@ class MessageHandler:
                 room_id, user_id, allow_departed_users=True
             )
 
-            if membership == Membership.JOIN:
+            if membership == Membership.JOIN or is_admin:
                 state_ids = await self.store.get_filtered_current_state_ids(
                     room_id, state_filter=state_filter
                 )
diff --git a/synapse/rest/admin/__init__.py b/synapse/rest/admin/__init__.py
index 6f7dc06503..4885f91ad8 100644
--- a/synapse/rest/admin/__init__.py
+++ b/synapse/rest/admin/__init__.py
@@ -41,6 +41,7 @@ from synapse.rest.admin.rooms import (
     MakeRoomAdminRestServlet,
     RoomMembersRestServlet,
     RoomRestServlet,
+    RoomStateRestServlet,
     ShutdownRoomRestServlet,
 )
 from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet
@@ -209,6 +210,7 @@ def register_servlets(hs, http_server):
     """
     register_servlets_for_client_rest_resource(hs, http_server)
     ListRoomRestServlet(hs).register(http_server)
+    RoomStateRestServlet(hs).register(http_server)
     RoomRestServlet(hs).register(http_server)
     RoomMembersRestServlet(hs).register(http_server)
     DeleteRoomRestServlet(hs).register(http_server)
diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py
index ab7cc9102a..4c0331b197 100644
--- a/synapse/rest/admin/rooms.py
+++ b/synapse/rest/admin/rooms.py
@@ -292,6 +292,38 @@ class RoomMembersRestServlet(RestServlet):
         return 200, ret
 
 
+class RoomStateRestServlet(RestServlet):
+    """
+    Get full state within a room.
+    """
+
+    PATTERNS = admin_patterns("/rooms/(?P<room_id>[^/]+)/state")
+
+    def __init__(self, hs: "HomeServer"):
+        self.hs = hs
+        self.auth = hs.get_auth()
+        self.store = hs.get_datastore()
+        self.message_handler = hs.get_message_handler()
+
+    async def on_GET(
+        self, request: SynapseRequest, room_id: str
+    ) -> Tuple[int, JsonDict]:
+        await assert_requester_is_admin(self.auth, request)
+
+        ret = await self.store.get_room(room_id)
+        if not ret:
+            raise NotFoundError("Room not found")
+
+        room_state = await self.message_handler.get_state_events(
+            user_id=request.requester.user.to_string(),
+            room_id=room_id,
+            is_admin=True,  # already verified above
+        )
+        ret = {"state": room_state}
+
+        return 200, ret
+
+
 class JoinRoomAliasServlet(RestServlet):
 
     PATTERNS = admin_patterns("/join/(?P<room_identifier>[^/]*)")
diff --git a/tests/rest/admin/test_room.py b/tests/rest/admin/test_room.py
index a0f32c5512..7c47aa7e0a 100644
--- a/tests/rest/admin/test_room.py
+++ b/tests/rest/admin/test_room.py
@@ -1180,6 +1180,21 @@ class RoomTestCase(unittest.HomeserverTestCase):
         )
         self.assertEqual(channel.json_body["total"], 3)
 
+    def test_room_state(self):
+        """Test that room state can be requested correctly"""
+        # Create two test rooms
+        room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
+
+        url = "/_synapse/admin/v1/rooms/%s/state" % (room_id,)
+        channel = self.make_request(
+            "GET", url.encode("ascii"), access_token=self.admin_user_tok,
+        )
+        self.assertEqual(200, channel.code, msg=channel.json_body)
+        self.assertIn("state", channel.json_body)
+        # testing that the state events match is painful and not done here. We assume that
+        # the create_room already does the right thing, so no need to verify that we got
+        # the state events it created.
+
 
 class JoinAliasRoomTestCase(unittest.HomeserverTestCase):