diff options
-rw-r--r-- | changelog.d/9167.feature | 1 | ||||
-rw-r--r-- | changelog.d/9168.feature | 1 | ||||
-rw-r--r-- | docs/admin_api/rooms.md | 30 | ||||
-rw-r--r-- | synapse/handlers/groups_local.py | 26 | ||||
-rw-r--r-- | synapse/handlers/message.py | 2 | ||||
-rw-r--r-- | synapse/rest/admin/__init__.py | 10 | ||||
-rw-r--r-- | synapse/rest/admin/groups.py | 60 | ||||
-rw-r--r-- | synapse/rest/admin/rooms.py | 39 | ||||
-rw-r--r-- | tests/rest/admin/test_room.py | 15 |
9 files changed, 181 insertions, 3 deletions
diff --git a/changelog.d/9167.feature b/changelog.d/9167.feature new file mode 100644 index 0000000000..6961620946 --- /dev/null +++ b/changelog.d/9167.feature @@ -0,0 +1 @@ +Add server admin endpoints to join users to legacy groups and manage their flair. \ No newline at end of file diff --git a/changelog.d/9168.feature b/changelog.d/9168.feature new file mode 100644 index 0000000000..8be1950eee --- /dev/null +++ b/changelog.d/9168.feature @@ -0,0 +1 @@ +Add an admin API for retrieving the current room state of a room. \ No newline at end of file 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/groups_local.py b/synapse/handlers/groups_local.py index df29edeb83..a2f16f77df 100644 --- a/synapse/handlers/groups_local.py +++ b/synapse/handlers/groups_local.py @@ -365,6 +365,32 @@ class GroupsLocalHandler(GroupsLocalWorkerHandler): return {} + async def force_join_user_to_group(self, group_id, user_id): + """Forces a user to join a group. + """ + if not self.is_mine_id(group_id): + raise SynapseError(400, "Can only affect local groups") + + if not self.is_mine_id(user_id): + raise SynapseError(400, "Can only affect local users") + + # Bypass the group server to avoid business logic regarding whether or not + # the user can actually join. + await self.store.add_user_to_group(group_id, user_id) + + token = await self.store.register_user_group_membership( + group_id, + user_id, + membership="join", + is_admin=False, + local_attestation=None, + remote_attestation=None, + is_publicised=False, + ) + self.notifier.on_new_event("groups_key", token, users=[user_id]) + + return {} + async def accept_invite(self, group_id, user_id, content): """Accept an invite to a group """ diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 9dfeab09cd..d3f8dc550d 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -174,7 +174,7 @@ class MessageHandler: 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 + self.storage, user_id, last_events, filter_send_to_client=False, ) event = last_events[0] diff --git a/synapse/rest/admin/__init__.py b/synapse/rest/admin/__init__.py index 6f7dc06503..a20a459874 100644 --- a/synapse/rest/admin/__init__.py +++ b/synapse/rest/admin/__init__.py @@ -31,7 +31,11 @@ from synapse.rest.admin.event_reports import ( EventReportDetailRestServlet, EventReportsRestServlet, ) -from synapse.rest.admin.groups import DeleteGroupAdminRestServlet +from synapse.rest.admin.groups import ( + DeleteGroupAdminRestServlet, + ForceJoinGroupAdminRestServlet, + UpdatePublicityGroupAdminRestServlet, +) from synapse.rest.admin.media import ListMediaInRoom, register_servlets_for_media_repo from synapse.rest.admin.purge_room_servlet import PurgeRoomServlet from synapse.rest.admin.rooms import ( @@ -41,6 +45,7 @@ from synapse.rest.admin.rooms import ( MakeRoomAdminRestServlet, RoomMembersRestServlet, RoomRestServlet, + RoomStateRestServlet, ShutdownRoomRestServlet, ) from synapse.rest.admin.server_notice_servlet import SendServerNoticeServlet @@ -209,6 +214,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) @@ -244,6 +250,8 @@ def register_servlets_for_client_rest_resource(hs, http_server): ShutdownRoomRestServlet(hs).register(http_server) UserRegisterServlet(hs).register(http_server) DeleteGroupAdminRestServlet(hs).register(http_server) + ForceJoinGroupAdminRestServlet(hs).register(http_server) + UpdatePublicityGroupAdminRestServlet(hs).register(http_server) AccountValidityRenewServlet(hs).register(http_server) # Load the media repo ones if we're using them. Otherwise load the servlets which diff --git a/synapse/rest/admin/groups.py b/synapse/rest/admin/groups.py index d0c86b204a..6af36e32b7 100644 --- a/synapse/rest/admin/groups.py +++ b/synapse/rest/admin/groups.py @@ -15,7 +15,11 @@ import logging from synapse.api.errors import SynapseError -from synapse.http.servlet import RestServlet +from synapse.http.servlet import ( + RestServlet, + assert_params_in_dict, + parse_json_object_from_request, +) from synapse.rest.admin._base import admin_patterns, assert_user_is_admin logger = logging.getLogger(__name__) @@ -41,3 +45,57 @@ class DeleteGroupAdminRestServlet(RestServlet): await self.group_server.delete_group(group_id, requester.user.to_string()) return 200, {} + + +class ForceJoinGroupAdminRestServlet(RestServlet): + """Allows a server admin to force-join a local user to a local group. + """ + + PATTERNS = admin_patterns("/group/(?P<group_id>[^/]*)/force_join$") + + def __init__(self, hs): + self.groups_handler = hs.get_groups_local_handler() + self.is_mine_id = hs.is_mine_id + self.auth = hs.get_auth() + + async def on_POST(self, request, group_id): + requester = await self.auth.get_user_by_req(request) + await assert_user_is_admin(self.auth, requester.user) + + if not self.is_mine_id(group_id): + raise SynapseError(400, "Can only affect local groups") + + body = parse_json_object_from_request(request, allow_empty_body=False) + assert_params_in_dict(body, ["user_id"]) + target_user_id = body["user_id"] + await self.groups_handler.force_join_user_to_group(group_id, target_user_id) + + return 200, {} + + +class UpdatePublicityGroupAdminRestServlet(RestServlet): + """Allows a server admin to update a user's publicity (flair) for a given group. + """ + + PATTERNS = admin_patterns("/group/(?P<group_id>[^/]*)/update_publicity$") + + def __init__(self, hs): + self.store = hs.get_datastore() + self.is_mine_id = hs.is_mine_id + self.auth = hs.get_auth() + + async def on_POST(self, request, group_id): + requester = await self.auth.get_user_by_req(request) + await assert_user_is_admin(self.auth, requester.user) + + body = parse_json_object_from_request(request, allow_empty_body=False) + assert_params_in_dict(body, ["user_id"]) + target_user_id = body["user_id"] + if not self.is_mine_id(target_user_id): + raise SynapseError(400, "Can only affect local users") + + # Logic copied from `/self/update_publicity` endpoint. + publicise = body["publicise"] + await self.store.update_group_publicity(group_id, target_user_id, publicise) + + return 200, {} diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py index ab7cc9102a..d903129dd2 100644 --- a/synapse/rest/admin/rooms.py +++ b/synapse/rest/admin/rooms.py @@ -292,6 +292,45 @@ 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.clock = hs.get_clock() + self._event_serializer = hs.get_event_client_serializer() + + async def on_GET( + self, request: SynapseRequest, room_id: str + ) -> Tuple[int, JsonDict]: + requester = await self.auth.get_user_by_req(request) + await assert_user_is_admin(self.auth, requester.user) + + ret = await self.store.get_room(room_id) + if not ret: + raise NotFoundError("Room not found") + + event_ids = await self.store.get_current_state_ids(room_id) + events = await self.store.get_events(event_ids.values()) + now = self.clock.time_msec() + room_state = await self._event_serializer.serialize_events( + events.values(), + now, + # We don't bother bundling aggregations in when asked for state + # events, as clients won't use them. + bundle_aggregations=False, + ) + 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): |