Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Add room details admin endpoint #7317

Merged
merged 9 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7317.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add room details admin endpoint. Contributed by Awesome Technologies Innovationslabor GmbH.
54 changes: 54 additions & 0 deletions docs/admin_api/rooms.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,57 @@ Response:

Once the `next_token` parameter is no longer present, we know we've reached the
end of the list.

# DRAFT: Room Details API
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved

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:
clokep marked this conversation as resolved.
Show resolved Hide resolved

```
{
"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
}
```
2 changes: 2 additions & 0 deletions synapse/rest/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from synapse.rest.admin.rooms import (
JoinRoomAliasServlet,
ListRoomRestServlet,
RoomRestServlet,
ShutdownRoomRestServlet,
)
from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet
Expand Down Expand Up @@ -193,6 +194,7 @@ def register_servlets(hs, http_server):
"""
register_servlets_for_client_rest_resource(hs, http_server)
ListRoomRestServlet(hs).register(http_server)
RoomRestServlet(hs).register(http_server)
JoinRoomAliasServlet(hs).register(http_server)
PurgeRoomServlet(hs).register(http_server)
SendServerNoticeServlet(hs).register(http_server)
Expand Down
26 changes: 25 additions & 1 deletion synapse/rest/admin/rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
)
from synapse.rest.admin._base import (
admin_patterns,
assert_requester_is_admin,
assert_user_is_admin,
historical_admin_path_patterns,
)
Expand Down Expand Up @@ -169,7 +170,7 @@ class ListRoomRestServlet(RestServlet):
in a dictionary containing room information. Supports pagination.
"""

PATTERNS = admin_patterns("/rooms")
PATTERNS = admin_patterns("/rooms$")

def __init__(self, hs):
self.store = hs.get_datastore()
Expand Down Expand Up @@ -253,6 +254,29 @@ async def on_GET(self, request):
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):

PATTERNS = admin_patterns("/join/(?P<room_identifier>[^/]*)")
Expand Down
31 changes: 31 additions & 0 deletions synapse/storage/data_stores/main/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,37 @@ def get_room(self, room_id):
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"])
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved
return res

return self.db.runInteraction(
"get_room_with_stats", get_room_with_stats_txn, room_id
)

def get_public_room_ids(self):
return self.db.simple_select_onecol(
table="rooms",
Expand Down
41 changes: 41 additions & 0 deletions tests/rest/admin/test_room.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,47 @@ def _search_test(
_search_test(None, "bar")
_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,
)
clokep marked this conversation as resolved.
Show resolved Hide resolved

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):

Expand Down
11 changes: 11 additions & 0 deletions tests/storage/test_room.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ def test_get_room(self):
(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):
@defer.inlineCallbacks
Expand Down