-
Notifications
You must be signed in to change notification settings - Fork 411
Add an Admin API to fetch an event by ID #18963
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
edb2e18
996262f
472aaee
8af1c33
c70d6f3
8536a4c
1cbf632
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add an Admin API to fetch an event by ID. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # Fetch Event API | ||
|
|
||
| The fetch event API allows admins to fetch an event regardless of their membership in the room it | ||
| originated in. Note that this endpoint will return rejected events, and you can verify that the event | ||
| has been rejected by checking if there is a `rejection_reason` field in the `unsigned` field of the event. | ||
|
|
||
| To use it, you will need to authenticate by providing an `access_token` | ||
| for a server admin: see [Admin API](../usage/administration/admin_api/). | ||
|
|
||
| Request: | ||
| ```http | ||
| GET /_synapse/admin/v1/fetch_event/<event_id> | ||
| ``` | ||
|
|
||
| The API returns a JSON body like the following: | ||
|
|
||
| Response: | ||
| ```json | ||
| { | ||
| "event": { | ||
| "auth_events": [ | ||
| "$WhLChbYg6atHuFRP7cUd95naUtc8L0f7fqeizlsUVvc", | ||
| "$9Wj8dt02lrNEWweeq-KjRABUYKba0K9DL2liRvsAdtQ", | ||
| "$qJxBFxBt8_ODd9b3pgOL_jXP98S_igc1_kizuPSZFi4" | ||
| ], | ||
| "content": { | ||
| "body": "Hey now", | ||
| "msgtype": "m.text" | ||
| }, | ||
| "depth": 6, | ||
| "event_id": "$hJ_kcXbVMcI82JDrbqfUJIHu61tJD86uIFJ_8hNHi7s", | ||
| "hashes": { | ||
| "sha256": "LiNw8DtrRVf55EgAH8R42Wz7WCJUqGsPt2We6qZO5Rg" | ||
| }, | ||
| "origin_server_ts": 799, | ||
| "prev_events": [ | ||
| "$cnSUrNMnC3Ywh9_W7EquFxYQjC_sT3BAAVzcUVxZq1g" | ||
| ], | ||
| "room_id": "!aIhKToCqgPTBloWMpf:test", | ||
| "sender": "@user:test", | ||
| "signatures": { | ||
| "test": { | ||
| "ed25519:a_lPym": "7mqSDwK1k7rnw34Dd8Fahu0rhPW7jPmcWPRtRDoEN9Yuv+BCM2+Rfdpv2MjxNKy3AYDEBwUwYEuaKMBaEMiKAQ" | ||
| } | ||
| }, | ||
| "type": "m.room.message", | ||
| "unsigned": { | ||
| "age_ts": 799 | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| from http import HTTPStatus | ||
| from typing import TYPE_CHECKING, Tuple | ||
|
|
||
| from synapse.api.errors import NotFoundError | ||
| from synapse.events.utils import ( | ||
| SerializeEventConfig, | ||
| format_event_raw, | ||
| serialize_event, | ||
| ) | ||
| from synapse.http.servlet import RestServlet | ||
| from synapse.http.site import SynapseRequest | ||
| from synapse.rest.admin import admin_patterns | ||
| from synapse.rest.admin._base import assert_user_is_admin | ||
| from synapse.storage.databases.main.events_worker import EventRedactBehaviour | ||
| from synapse.types import JsonDict | ||
|
|
||
| if TYPE_CHECKING: | ||
| from synapse.server import HomeServer | ||
|
|
||
|
|
||
| class EventRestServlet(RestServlet): | ||
| """ | ||
| Get an event that is known to the homeserver. | ||
| The requester must have administrator access in Synapse. | ||
|
|
||
| GET /_synapse/admin/v1/fetch_event/<event_id> | ||
| returns: | ||
| 200 OK with event json if the event is known to the homeserver. Otherwise raises | ||
| a NotFound error. | ||
|
|
||
| Args: | ||
| event_id: the id of the requested event. | ||
| Returns: | ||
| JSON blob of the event | ||
| """ | ||
|
|
||
| PATTERNS = admin_patterns("/fetch_event/(?P<event_id>[^/]*)$") | ||
|
|
||
| def __init__(self, hs: "HomeServer"): | ||
| self._auth = hs.get_auth() | ||
| self._store = hs.get_datastores().main | ||
| self._clock = hs.get_clock() | ||
|
|
||
| async def on_GET( | ||
| self, request: SynapseRequest, event_id: str | ||
| ) -> Tuple[int, JsonDict]: | ||
| requester = await self._auth.get_user_by_req(request) | ||
| await assert_user_is_admin(self._auth, requester) | ||
|
|
||
| event = await self._store.get_event( | ||
| event_id, EventRedactBehaviour.as_is, allow_none=True, allow_rejected=True | ||
|
||
| ) | ||
|
|
||
| if event is None: | ||
| raise NotFoundError("Event not found") | ||
|
|
||
| config = SerializeEventConfig( | ||
| as_client_event=False, | ||
| event_format=format_event_raw, | ||
| requester=requester, | ||
| only_event_fields=None, | ||
| include_stripped_room_state=True, | ||
| include_admin_metadata=True, | ||
| ) | ||
| res = {"event": serialize_event(event, self._clock.time_msec(), config=config)} | ||
|
|
||
| return HTTPStatus.OK, res | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| from twisted.internet.testing import MemoryReactor | ||
|
|
||
| import synapse.rest.admin | ||
| from synapse.api.errors import Codes | ||
| from synapse.rest.client import login, room | ||
| from synapse.server import HomeServer | ||
| from synapse.util.clock import Clock | ||
|
|
||
| from tests import unittest | ||
|
|
||
|
|
||
| class FetchEventTestCase(unittest.HomeserverTestCase): | ||
| servlets = [ | ||
| synapse.rest.admin.register_servlets, | ||
| login.register_servlets, | ||
| room.register_servlets, | ||
| ] | ||
|
|
||
| def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: | ||
| self.admin_user = self.register_user("admin", "pass", admin=True) | ||
| self.admin_user_tok = self.login("admin", "pass") | ||
|
|
||
| self.other_user = self.register_user("user", "pass") | ||
| self.other_user_tok = self.login("user", "pass") | ||
|
|
||
| self.room_id1 = self.helper.create_room_as( | ||
| self.other_user, tok=self.other_user_tok, is_public=True | ||
| ) | ||
| resp = self.helper.send(self.room_id1, body="Hey now", tok=self.other_user_tok) | ||
| self.event_id = resp["event_id"] | ||
|
|
||
| def test_no_auth(self) -> None: | ||
| """ | ||
| Try to get an event without authentication. | ||
| """ | ||
| channel = self.make_request( | ||
| "GET", | ||
| f"/_synapse/admin/v1/fetch_event/{self.event_id}", | ||
| ) | ||
|
|
||
| self.assertEqual(401, channel.code, msg=channel.json_body) | ||
| self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"]) | ||
|
|
||
| def test_requester_is_not_admin(self) -> None: | ||
| """ | ||
| If the user is not a server admin, an error 403 is returned. | ||
| """ | ||
|
|
||
| channel = self.make_request( | ||
| "GET", | ||
| f"/_synapse/admin/v1/fetch_event/{self.event_id}", | ||
| access_token=self.other_user_tok, | ||
| ) | ||
|
|
||
| self.assertEqual(403, channel.code, msg=channel.json_body) | ||
| self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"]) | ||
|
|
||
| def test_fetch_event(self) -> None: | ||
| """ | ||
| Test that we can successfully fetch an event | ||
| """ | ||
| channel = self.make_request( | ||
| "GET", | ||
| f"/_synapse/admin/v1/fetch_event/{self.event_id}", | ||
| access_token=self.admin_user_tok, | ||
| ) | ||
| self.assertEqual(200, channel.code, msg=channel.json_body) | ||
| self.assertEqual( | ||
| channel.json_body["event"]["content"], | ||
| {"body": "Hey now", "msgtype": "m.text"}, | ||
| ) | ||
| self.assertEqual(channel.json_body["event"]["event_id"], self.event_id) | ||
| self.assertEqual(channel.json_body["event"]["type"], "m.room.message") | ||
| self.assertEqual(channel.json_body["event"]["sender"], self.other_user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heads up this file wasn't added to SUMMARY.md, meaning the page wouldn't appear in the sidebar of the documentation website.
I did so in a commit during the 1.140.0rc1 release process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! Apologies for the oversight, thank you for adding it.