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

Commit b0b2cac

Browse files
authored
Merge pull request #9150 from Yoric/develop-context
New API /_synapse/admin/rooms/{roomId}/context/{eventId}
2 parents d882fbc + 31d072a commit b0b2cac

File tree

8 files changed

+289
-6
lines changed

8 files changed

+289
-6
lines changed

changelog.d/9150.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New API /_synapse/admin/rooms/{roomId}/context/{eventId}.

docs/admin_api/rooms.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* [Undoing room shutdowns](#undoing-room-shutdowns)
1111
- [Make Room Admin API](#make-room-admin-api)
1212
- [Forward Extremities Admin API](#forward-extremities-admin-api)
13+
- [Event Context API](#event-context-api)
1314

1415
# List Room API
1516

@@ -594,3 +595,121 @@ that were deleted.
594595
"deleted": 1
595596
}
596597
```
598+
599+
# Event Context API
600+
601+
This API lets a client find the context of an event. This is designed primarily to investigate abuse reports.
602+
603+
```
604+
GET /_synapse/admin/v1/rooms/<room_id>/context/<event_id>
605+
```
606+
607+
This API mimmicks [GET /_matrix/client/r0/rooms/{roomId}/context/{eventId}](https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-context-eventid). Please refer to the link for all details on parameters and reseponse.
608+
609+
Example response:
610+
611+
```json
612+
{
613+
"end": "t29-57_2_0_2",
614+
"events_after": [
615+
{
616+
"content": {
617+
"body": "This is an example text message",
618+
"msgtype": "m.text",
619+
"format": "org.matrix.custom.html",
620+
"formatted_body": "<b>This is an example text message</b>"
621+
},
622+
"type": "m.room.message",
623+
"event_id": "$143273582443PhrSn:example.org",
624+
"room_id": "!636q39766251:example.com",
625+
"sender": "@example:example.org",
626+
"origin_server_ts": 1432735824653,
627+
"unsigned": {
628+
"age": 1234
629+
}
630+
}
631+
],
632+
"event": {
633+
"content": {
634+
"body": "filename.jpg",
635+
"info": {
636+
"h": 398,
637+
"w": 394,
638+
"mimetype": "image/jpeg",
639+
"size": 31037
640+
},
641+
"url": "mxc://example.org/JWEIFJgwEIhweiWJE",
642+
"msgtype": "m.image"
643+
},
644+
"type": "m.room.message",
645+
"event_id": "$f3h4d129462ha:example.com",
646+
"room_id": "!636q39766251:example.com",
647+
"sender": "@example:example.org",
648+
"origin_server_ts": 1432735824653,
649+
"unsigned": {
650+
"age": 1234
651+
}
652+
},
653+
"events_before": [
654+
{
655+
"content": {
656+
"body": "something-important.doc",
657+
"filename": "something-important.doc",
658+
"info": {
659+
"mimetype": "application/msword",
660+
"size": 46144
661+
},
662+
"msgtype": "m.file",
663+
"url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe"
664+
},
665+
"type": "m.room.message",
666+
"event_id": "$143273582443PhrSn:example.org",
667+
"room_id": "!636q39766251:example.com",
668+
"sender": "@example:example.org",
669+
"origin_server_ts": 1432735824653,
670+
"unsigned": {
671+
"age": 1234
672+
}
673+
}
674+
],
675+
"start": "t27-54_2_0_2",
676+
"state": [
677+
{
678+
"content": {
679+
"creator": "@example:example.org",
680+
"room_version": "1",
681+
"m.federate": true,
682+
"predecessor": {
683+
"event_id": "$something:example.org",
684+
"room_id": "!oldroom:example.org"
685+
}
686+
},
687+
"type": "m.room.create",
688+
"event_id": "$143273582443PhrSn:example.org",
689+
"room_id": "!636q39766251:example.com",
690+
"sender": "@example:example.org",
691+
"origin_server_ts": 1432735824653,
692+
"unsigned": {
693+
"age": 1234
694+
},
695+
"state_key": ""
696+
},
697+
{
698+
"content": {
699+
"membership": "join",
700+
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
701+
"displayname": "Alice Margatroid"
702+
},
703+
"type": "m.room.member",
704+
"event_id": "$143273582443PhrSn:example.org",
705+
"room_id": "!636q39766251:example.com",
706+
"sender": "@example:example.org",
707+
"origin_server_ts": 1432735824653,
708+
"unsigned": {
709+
"age": 1234
710+
},
711+
"state_key": "@alice:example.org"
712+
}
713+
]
714+
}
715+
```

synapse/handlers/room.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
3939
from synapse.events import EventBase
4040
from synapse.events.utils import copy_power_levels_contents
41+
from synapse.rest.admin._base import assert_user_is_admin
4142
from synapse.storage.state import StateFilter
4243
from synapse.types import (
4344
JsonDict,
@@ -1004,41 +1005,51 @@ async def _generate_room_id(
10041005
class RoomContextHandler:
10051006
def __init__(self, hs: "HomeServer"):
10061007
self.hs = hs
1008+
self.auth = hs.get_auth()
10071009
self.store = hs.get_datastore()
10081010
self.storage = hs.get_storage()
10091011
self.state_store = self.storage.state
10101012

10111013
async def get_event_context(
10121014
self,
1013-
user: UserID,
1015+
requester: Requester,
10141016
room_id: str,
10151017
event_id: str,
10161018
limit: int,
10171019
event_filter: Optional[Filter],
1020+
use_admin_priviledge: bool = False,
10181021
) -> Optional[JsonDict]:
10191022
"""Retrieves events, pagination tokens and state around a given event
10201023
in a room.
10211024
10221025
Args:
1023-
user
1026+
requester
10241027
room_id
10251028
event_id
10261029
limit: The maximum number of events to return in total
10271030
(excluding state).
10281031
event_filter: the filter to apply to the events returned
10291032
(excluding the target event_id)
1030-
1033+
use_admin_priviledge: if `True`, return all events, regardless
1034+
of whether `user` has access to them. To be used **ONLY**
1035+
from the admin API.
10311036
Returns:
10321037
dict, or None if the event isn't found
10331038
"""
1039+
user = requester.user
1040+
if use_admin_priviledge:
1041+
await assert_user_is_admin(self.auth, requester.user)
1042+
10341043
before_limit = math.floor(limit / 2.0)
10351044
after_limit = limit - before_limit
10361045

10371046
users = await self.store.get_users_in_room(room_id)
10381047
is_peeking = user.to_string() not in users
10391048

1040-
def filter_evts(events):
1041-
return filter_events_for_client(
1049+
async def filter_evts(events):
1050+
if use_admin_priviledge:
1051+
return events
1052+
return await filter_events_for_client(
10421053
self.storage, user.to_string(), events, is_peeking=is_peeking
10431054
)
10441055

synapse/rest/admin/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
JoinRoomAliasServlet,
4343
ListRoomRestServlet,
4444
MakeRoomAdminRestServlet,
45+
RoomEventContextServlet,
4546
RoomMembersRestServlet,
4647
RoomRestServlet,
4748
RoomStateRestServlet,
@@ -238,6 +239,7 @@ def register_servlets(hs, http_server):
238239
MakeRoomAdminRestServlet(hs).register(http_server)
239240
ShadowBanRestServlet(hs).register(http_server)
240241
ForwardExtremitiesRestServlet(hs).register(http_server)
242+
RoomEventContextServlet(hs).register(http_server)
241243

242244

243245
def register_servlets_for_client_rest_resource(hs, http_server):

synapse/rest/admin/rooms.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
import logging
1616
from http import HTTPStatus
1717
from typing import TYPE_CHECKING, List, Optional, Tuple
18+
from urllib import parse as urlparse
1819

1920
from synapse.api.constants import EventTypes, JoinRules, Membership
2021
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
22+
from synapse.api.filtering import Filter
2123
from synapse.http.servlet import (
2224
RestServlet,
2325
assert_params_in_dict,
@@ -33,6 +35,7 @@
3335
)
3436
from synapse.storage.databases.main.room import RoomSortOrder
3537
from synapse.types import JsonDict, RoomAlias, RoomID, UserID, create_requester
38+
from synapse.util import json_decoder
3639

3740
if TYPE_CHECKING:
3841
from synapse.server import HomeServer
@@ -605,3 +608,65 @@ async def on_GET(self, request, room_identifier):
605608

606609
extremities = await self.store.get_forward_extremities_for_room(room_id)
607610
return 200, {"count": len(extremities), "results": extremities}
611+
612+
613+
class RoomEventContextServlet(RestServlet):
614+
"""
615+
Provide the context for an event.
616+
This API is designed to be used when system administrators wish to look at
617+
an abuse report and understand what happened during and immediately prior
618+
to this event.
619+
"""
620+
621+
PATTERNS = admin_patterns("/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$")
622+
623+
def __init__(self, hs):
624+
super().__init__()
625+
self.clock = hs.get_clock()
626+
self.room_context_handler = hs.get_room_context_handler()
627+
self._event_serializer = hs.get_event_client_serializer()
628+
self.auth = hs.get_auth()
629+
630+
async def on_GET(self, request, room_id, event_id):
631+
requester = await self.auth.get_user_by_req(request, allow_guest=False)
632+
await assert_user_is_admin(self.auth, requester.user)
633+
634+
limit = parse_integer(request, "limit", default=10)
635+
636+
# picking the API shape for symmetry with /messages
637+
filter_str = parse_string(request, b"filter", encoding="utf-8")
638+
if filter_str:
639+
filter_json = urlparse.unquote(filter_str)
640+
event_filter = Filter(
641+
json_decoder.decode(filter_json)
642+
) # type: Optional[Filter]
643+
else:
644+
event_filter = None
645+
646+
results = await self.room_context_handler.get_event_context(
647+
requester,
648+
room_id,
649+
event_id,
650+
limit,
651+
event_filter,
652+
use_admin_priviledge=True,
653+
)
654+
655+
if not results:
656+
raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
657+
658+
time_now = self.clock.time_msec()
659+
results["events_before"] = await self._event_serializer.serialize_events(
660+
results["events_before"], time_now
661+
)
662+
results["event"] = await self._event_serializer.serialize_event(
663+
results["event"], time_now
664+
)
665+
results["events_after"] = await self._event_serializer.serialize_events(
666+
results["events_after"], time_now
667+
)
668+
results["state"] = await self._event_serializer.serialize_events(
669+
results["state"], time_now
670+
)
671+
672+
return 200, results

synapse/rest/client/v1/room.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ async def on_GET(self, request, room_id, event_id):
650650
event_filter = None
651651

652652
results = await self.room_context_handler.get_event_context(
653-
requester.user, room_id, event_id, limit, event_filter
653+
requester, room_id, event_id, limit, event_filter
654654
)
655655

656656
if not results:

synapse/visibility.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ async def filter_events_for_client(
8080
events = [e for e in events if not e.internal_metadata.is_soft_failed()]
8181

8282
types = ((EventTypes.RoomHistoryVisibility, ""), (EventTypes.Member, user_id))
83+
8384
event_id_to_state = await storage.state.get_state_for_events(
8485
frozenset(e.event_id for e in events),
8586
state_filter=StateFilter.from_types(types),

0 commit comments

Comments
 (0)