Compare commits
10 Commits
v1.99.0
...
travis/fos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a06dd1d6b5 | ||
|
|
fc2cbce232 | ||
|
|
f7a03e86e0 | ||
|
|
d9867f1640 | ||
|
|
7d8cc63e37 | ||
|
|
19a4821ffc | ||
|
|
40f96320a2 | ||
|
|
e2377bba70 | ||
|
|
84204f8020 | ||
|
|
95d7074322 |
1
changelog.d/9167.feature
Normal file
1
changelog.d/9167.feature
Normal 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
1
changelog.d/9168.feature
Normal file
@@ -0,0 +1 @@
|
||||
Add an admin API for retrieving the current room state of a room.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, {}
|
||||
|
||||
@@ -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>[^/]*)")
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user