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 7 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.
55 changes: 55 additions & 0 deletions docs/admin_api/rooms.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,58 @@ 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,
including a list of all room members.
awesome-manuel marked this conversation as resolved.
Show resolved Hide resolved

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