mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-14 11:57:44 +00:00
Add room details admin endpoint (#7317)
This commit is contained in:
parent
5bb26b7c4f
commit
a4a5ec4096
7 changed files with 165 additions and 1 deletions
1
changelog.d/7317.feature
Normal file
1
changelog.d/7317.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add room details admin endpoint. Contributed by Awesome Technologies Innovationslabor GmbH.
|
|
@ -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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>[^/]*)")
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue