Compare commits

...

10 Commits

Author SHA1 Message Date
Dan Callahan
a06dd1d6b5 Merge tag 'v1.26.0rc2' into travis/fosdem/hotfixes 2021-01-26 15:38:22 +00:00
Travis Ralston
fc2cbce232 Fix state endpoint to be faster 2021-01-22 12:37:42 -07:00
Travis Ralston
f7a03e86e0 Merge branch 'travis/fosdem/admin-api-room-state' into travis/fosdem/hotfixes 2021-01-21 12:35:05 -07:00
Travis Ralston
d9867f1640 Merge branch 'travis/fosdem/admin-api-groups' into travis/fosdem/hotfixes 2021-01-21 12:34:59 -07:00
Travis Ralston
7d8cc63e37 Get the right requester object 2021-01-19 14:03:39 -07:00
Travis Ralston
19a4821ffc Changelog 2021-01-19 14:01:08 -07:00
Travis Ralston
40f96320a2 Add an admin API to get the current room state
This could arguably replace the existing admin API for `/members`, however that is out of scope of this change.

This sort of endpoint is ideal for moderation use cases as well as other applications, such as needing to retrieve various bits of information about a room to perform a task (like syncing power levels between two places). This endpoint exposes nothing more than an admin would be able to access with a `select *` query on their database.
2021-01-19 13:59:29 -07:00
Travis Ralston
e2377bba70 Appease the linters 2021-01-19 13:25:10 -07:00
Travis Ralston
84204f8020 Changelog 2021-01-19 13:23:40 -07:00
Travis Ralston
95d7074322 Add admin APIs to force-join users to groups and manage their flair
Fixes https://github.com/matrix-org/synapse/issues/9143

Though the groups API is disappearing soon, these functions are intended to make flair management easier in the short term.
2021-01-19 13:21:17 -07:00
9 changed files with 181 additions and 3 deletions

1
changelog.d/9167.feature Normal file
View File

@@ -0,0 +1 @@
Add server admin endpoints to join users to legacy groups and manage their flair.

1
changelog.d/9168.feature Normal file
View File

@@ -0,0 +1 @@
Add an admin API for retrieving the current room state of a room.

View File

@@ -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

View File

@@ -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
"""

View File

@@ -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]

View File

@@ -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

View File

@@ -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, {}

View File

@@ -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>[^/]*)")

View File

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