Add room details admin endpoint (#7317)

This commit is contained in:
Manuel Stahl 2020-05-07 21:33:07 +02:00 committed by GitHub
parent 5bb26b7c4f
commit a4a5ec4096
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 1 deletions

1
changelog.d/7317.feature Normal file
View file

@ -0,0 +1 @@
Add room details admin endpoint. Contributed by Awesome Technologies Innovationslabor GmbH.

View file

@ -264,3 +264,57 @@ Response:
Once the `next_token` parameter is no longer present, we know we've reached the Once the `next_token` parameter is no longer present, we know we've reached the
end of the list. end of the list.
# DRAFT: Room Details API
The Room Details admin API allows server admins to get all details of a room.
This API is still a draft and details might change!
The following fields are possible in the JSON response body:
* `room_id` - The ID of the room.
* `name` - The name of the room.
* `canonical_alias` - The canonical (main) alias address of the room.
* `joined_members` - How many users are currently in the room.
* `joined_local_members` - How many local users are currently in the room.
* `version` - The version of the room as a string.
* `creator` - The `user_id` of the room creator.
* `encryption` - Algorithm of end-to-end encryption of messages. Is `null` if encryption is not active.
* `federatable` - Whether users on other servers can join this room.
* `public` - Whether the room is visible in room directory.
* `join_rules` - The type of rules used for users wishing to join this room. One of: ["public", "knock", "invite", "private"].
* `guest_access` - Whether guests can join the room. One of: ["can_join", "forbidden"].
* `history_visibility` - Who can see the room history. One of: ["invited", "joined", "shared", "world_readable"].
* `state_events` - Total number of state_events of a room. Complexity of the room.
## Usage
A standard request:
```
GET /_synapse/admin/v1/rooms/<room_id>
{}
```
Response:
```
{
"room_id": "!mscvqgqpHYjBGDxNym:matrix.org",
"name": "Music Theory",
"canonical_alias": "#musictheory:matrix.org",
"joined_members": 127
"joined_local_members": 2,
"version": "1",
"creator": "@foo:matrix.org",
"encryption": null,
"federatable": true,
"public": true,
"join_rules": "invite",
"guest_access": null,
"history_visibility": "shared",
"state_events": 93534
}
```

View file

@ -32,6 +32,7 @@ from synapse.rest.admin.purge_room_servlet import PurgeRoomServlet
from synapse.rest.admin.rooms import ( from synapse.rest.admin.rooms import (
JoinRoomAliasServlet, JoinRoomAliasServlet,
ListRoomRestServlet, ListRoomRestServlet,
RoomRestServlet,
ShutdownRoomRestServlet, ShutdownRoomRestServlet,
) )
from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet
@ -193,6 +194,7 @@ def register_servlets(hs, http_server):
""" """
register_servlets_for_client_rest_resource(hs, http_server) register_servlets_for_client_rest_resource(hs, http_server)
ListRoomRestServlet(hs).register(http_server) ListRoomRestServlet(hs).register(http_server)
RoomRestServlet(hs).register(http_server)
JoinRoomAliasServlet(hs).register(http_server) JoinRoomAliasServlet(hs).register(http_server)
PurgeRoomServlet(hs).register(http_server) PurgeRoomServlet(hs).register(http_server)
SendServerNoticeServlet(hs).register(http_server) SendServerNoticeServlet(hs).register(http_server)

View file

@ -26,6 +26,7 @@ from synapse.http.servlet import (
) )
from synapse.rest.admin._base import ( from synapse.rest.admin._base import (
admin_patterns, admin_patterns,
assert_requester_is_admin,
assert_user_is_admin, assert_user_is_admin,
historical_admin_path_patterns, historical_admin_path_patterns,
) )
@ -169,7 +170,7 @@ class ListRoomRestServlet(RestServlet):
in a dictionary containing room information. Supports pagination. in a dictionary containing room information. Supports pagination.
""" """
PATTERNS = admin_patterns("/rooms") PATTERNS = admin_patterns("/rooms$")
def __init__(self, hs): def __init__(self, hs):
self.store = hs.get_datastore() self.store = hs.get_datastore()
@ -253,6 +254,29 @@ class ListRoomRestServlet(RestServlet):
return 200, response return 200, response
class RoomRestServlet(RestServlet):
"""Get room details.
TODO: Add on_POST to allow room creation without joining the room
"""
PATTERNS = admin_patterns("/rooms/(?P<room_id>[^/]+)$")
def __init__(self, hs):
self.hs = hs
self.auth = hs.get_auth()
self.store = hs.get_datastore()
async def on_GET(self, request, room_id):
await assert_requester_is_admin(self.auth, request)
ret = await self.store.get_room_with_stats(room_id)
if not ret:
raise NotFoundError("Room not found")
return 200, ret
class JoinRoomAliasServlet(RestServlet): class JoinRoomAliasServlet(RestServlet):
PATTERNS = admin_patterns("/join/(?P<room_identifier>[^/]*)") PATTERNS = admin_patterns("/join/(?P<room_identifier>[^/]*)")

View file

@ -98,6 +98,37 @@ class RoomWorkerStore(SQLBaseStore):
allow_none=True, allow_none=True,
) )
def get_room_with_stats(self, room_id: str):
"""Retrieve room with statistics.
Args:
room_id: The ID of the room to retrieve.
Returns:
A dict containing the room information, or None if the room is unknown.
"""
def get_room_with_stats_txn(txn, room_id):
sql = """
SELECT room_id, state.name, state.canonical_alias, curr.joined_members,
curr.local_users_in_room AS joined_local_members, rooms.room_version AS version,
rooms.creator, state.encryption, state.is_federatable AS federatable,
rooms.is_public AS public, state.join_rules, state.guest_access,
state.history_visibility, curr.current_state_events AS state_events
FROM rooms
LEFT JOIN room_stats_state state USING (room_id)
LEFT JOIN room_stats_current curr USING (room_id)
WHERE room_id = ?
"""
txn.execute(sql, [room_id])
res = self.db.cursor_to_dict(txn)[0]
res["federatable"] = bool(res["federatable"])
res["public"] = bool(res["public"])
return res
return self.db.runInteraction(
"get_room_with_stats", get_room_with_stats_txn, room_id
)
def get_public_room_ids(self): def get_public_room_ids(self):
return self.db.simple_select_onecol( return self.db.simple_select_onecol(
table="rooms", table="rooms",

View file

@ -701,6 +701,47 @@ class RoomTestCase(unittest.HomeserverTestCase):
_search_test(None, "bar") _search_test(None, "bar")
_search_test(None, "", expected_http_code=400) _search_test(None, "", expected_http_code=400)
def test_single_room(self):
"""Test that a single room can be requested correctly"""
# Create two test rooms
room_id_1 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
room_id_2 = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
room_name_1 = "something"
room_name_2 = "else"
# Set the name for each room
self.helper.send_state(
room_id_1, "m.room.name", {"name": room_name_1}, tok=self.admin_user_tok,
)
self.helper.send_state(
room_id_2, "m.room.name", {"name": room_name_2}, tok=self.admin_user_tok,
)
url = "/_synapse/admin/v1/rooms/%s" % (room_id_1,)
request, channel = self.make_request(
"GET", url.encode("ascii"), access_token=self.admin_user_tok,
)
self.render(request)
self.assertEqual(200, channel.code, msg=channel.json_body)
self.assertIn("room_id", channel.json_body)
self.assertIn("name", channel.json_body)
self.assertIn("canonical_alias", channel.json_body)
self.assertIn("joined_members", channel.json_body)
self.assertIn("joined_local_members", channel.json_body)
self.assertIn("version", channel.json_body)
self.assertIn("creator", channel.json_body)
self.assertIn("encryption", channel.json_body)
self.assertIn("federatable", channel.json_body)
self.assertIn("public", channel.json_body)
self.assertIn("join_rules", channel.json_body)
self.assertIn("guest_access", channel.json_body)
self.assertIn("history_visibility", channel.json_body)
self.assertIn("state_events", channel.json_body)
self.assertEqual(room_id_1, channel.json_body["room_id"])
class JoinAliasRoomTestCase(unittest.HomeserverTestCase): class JoinAliasRoomTestCase(unittest.HomeserverTestCase):

View file

@ -55,6 +55,17 @@ class RoomStoreTestCase(unittest.TestCase):
(yield self.store.get_room(self.room.to_string())), (yield self.store.get_room(self.room.to_string())),
) )
@defer.inlineCallbacks
def test_get_room_with_stats(self):
self.assertDictContainsSubset(
{
"room_id": self.room.to_string(),
"creator": self.u_creator.to_string(),
"public": True,
},
(yield self.store.get_room_with_stats(self.room.to_string())),
)
class RoomEventsStoreTestCase(unittest.TestCase): class RoomEventsStoreTestCase(unittest.TestCase):
@defer.inlineCallbacks @defer.inlineCallbacks