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

Fix /room/.../event/... to return the *original* event after any edits #12476

Merged
merged 2 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/12476.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long-standing bug which incorrectly caused `GET /_matrix/client/r3/rooms/{roomId}/event/{eventId}` to return edited events rather than the original.
21 changes: 13 additions & 8 deletions synapse/events/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,17 +402,18 @@ def serialize_event(
*,
config: SerializeEventConfig = _DEFAULT_SERIALIZE_EVENT_CONFIG,
bundle_aggregations: Optional[Dict[str, "BundledAggregations"]] = None,
apply_edits: bool = True,
) -> JsonDict:
"""Serializes a single event.

Args:
event: The event being serialized.
time_now: The current time in milliseconds
config: Event serialization config
bundle_aggregations: Whether to include the bundled aggregations for this
event. Only applies to non-state events. (State events never include
bundled aggregations.)

bundle_aggregations: A map from event_id to the aggregations to be bundled
into the event.
apply_edits: Whether the content of the event should be modified to reflect
any replacement in `bundle_aggregations[<event_id>].replace`.
Returns:
The serialized event
"""
Expand All @@ -430,8 +431,9 @@ def serialize_event(
event,
time_now,
config,
bundle_aggregations[event.event_id],
event_aggregations,
serialized_event,
apply_edits=apply_edits,
)

return serialized_event
Expand Down Expand Up @@ -470,6 +472,7 @@ def _inject_bundled_aggregations(
config: SerializeEventConfig,
aggregations: "BundledAggregations",
serialized_event: JsonDict,
apply_edits: bool,
) -> None:
"""Potentially injects bundled aggregations into the unsigned portion of the serialized event.

Expand All @@ -479,7 +482,8 @@ def _inject_bundled_aggregations(
aggregations: The bundled aggregation to serialize.
serialized_event: The serialized event which may be modified.
config: Event serialization config

apply_edits: Whether the content of the event should be modified to reflect
any replacement in `aggregations.replace`.
"""
serialized_aggregations = {}

Expand All @@ -490,9 +494,10 @@ def _inject_bundled_aggregations(
serialized_aggregations[RelationTypes.REFERENCE] = aggregations.references

if aggregations.replace:
# If there is an edit, apply it to the event.
# If there is an edit, optionally apply it to the event.
edit = aggregations.replace
self._apply_edit(event, serialized_event, edit)
if apply_edits:
clokep marked this conversation as resolved.
Show resolved Hide resolved
self._apply_edit(event, serialized_event, edit)

# Include information about it in the relations dict.
serialized_aggregations[RelationTypes.REPLACE] = {
Expand Down
4 changes: 3 additions & 1 deletion synapse/rest/client/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,8 +669,10 @@ async def on_GET(
)

time_now = self.clock.time_msec()
# per MSC2676, /rooms/{roomId}/event/{eventId}, should return the
# *original* event, rather than the edited version
event_dict = self._event_serializer.serialize_event(
event, time_now, bundle_aggregations=aggregations
event, time_now, bundle_aggregations=aggregations, apply_edits=False
)
return 200, event_dict

Expand Down
92 changes: 62 additions & 30 deletions tests/rest/client/test_relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,13 +380,16 @@ def assert_bundle(event_json: JsonDict) -> None:
{"event_id": edit_event_id, "sender": self.user_id}, m_replace_dict
)

# /event should return the *original* event
channel = self.make_request(
"GET",
f"/rooms/{self.room}/event/{self.parent_id}",
access_token=self.user_token,
)
self.assertEqual(200, channel.code, channel.json_body)
self.assertEqual(channel.json_body["content"], new_body)
self.assertEqual(
channel.json_body["content"], {"body": "Hi!", "msgtype": "m.text"}
)
assert_bundle(channel.json_body)

# Request the room messages.
Expand All @@ -399,13 +402,15 @@ def assert_bundle(event_json: JsonDict) -> None:
assert_bundle(self._find_event_in_chunk(channel.json_body["chunk"]))

# Request the room context.
# /context should return the edited event.
channel = self.make_request(
"GET",
f"/rooms/{self.room}/context/{self.parent_id}",
access_token=self.user_token,
)
self.assertEqual(200, channel.code, channel.json_body)
assert_bundle(channel.json_body["event"])
self.assertEqual(channel.json_body["event"]["content"], new_body)

# Request sync, but limit the timeline so it becomes limited (and includes
# bundled aggregations).
Expand Down Expand Up @@ -470,14 +475,14 @@ def test_multi_edit(self) -> None:

channel = self.make_request(
"GET",
f"/rooms/{self.room}/event/{self.parent_id}",
f"/rooms/{self.room}/context/{self.parent_id}",
access_token=self.user_token,
)
self.assertEqual(200, channel.code, channel.json_body)

self.assertEqual(channel.json_body["content"], new_body)
self.assertEqual(channel.json_body["event"]["content"], new_body)

relations_dict = channel.json_body["unsigned"].get("m.relations")
relations_dict = channel.json_body["event"]["unsigned"].get("m.relations")
self.assertIn(RelationTypes.REPLACE, relations_dict)

m_replace_dict = relations_dict[RelationTypes.REPLACE]
Expand All @@ -492,10 +497,9 @@ def test_edit_reply(self) -> None:
"""Test that editing a reply works."""

# Create a reply to edit.
original_body = {"msgtype": "m.text", "body": "A reply!"}
channel = self._send_relation(
RelationTypes.REFERENCE,
"m.room.message",
content={"msgtype": "m.text", "body": "A reply!"},
RelationTypes.REFERENCE, "m.room.message", content=original_body
)
reply = channel.json_body["event_id"]

Expand All @@ -508,38 +512,54 @@ def test_edit_reply(self) -> None:
)
edit_event_id = channel.json_body["event_id"]

# /event returns the original event
channel = self.make_request(
"GET",
f"/rooms/{self.room}/event/{reply}",
access_token=self.user_token,
)
self.assertEqual(200, channel.code, channel.json_body)
event_result = channel.json_body
self.assertDictContainsSubset(original_body, event_result["content"])

# We expect to see the new body in the dict, as well as the reference
# metadata sill intact.
self.assertDictContainsSubset(new_body, channel.json_body["content"])
self.assertDictContainsSubset(
{
"m.relates_to": {
"event_id": self.parent_id,
"rel_type": "m.reference",
}
},
channel.json_body["content"],
# also check /context, which returns the *edited* event
channel = self.make_request(
"GET",
f"/rooms/{self.room}/context/{reply}",
access_token=self.user_token,
)
self.assertEqual(200, channel.code, channel.json_body)
context_result = channel.json_body["event"]

# We expect that the edit relation appears in the unsigned relations
# section.
relations_dict = channel.json_body["unsigned"].get("m.relations")
self.assertIn(RelationTypes.REPLACE, relations_dict)
# check that the relations are correct for both APIs
for result_event_dict, desc in (
(event_result, "/event"),
(context_result, "/context"),
):
# The reference metadata should still be intact.
self.assertDictContainsSubset(
{
"m.relates_to": {
"event_id": self.parent_id,
"rel_type": "m.reference",
}
},
result_event_dict["content"],
desc,
)

m_replace_dict = relations_dict[RelationTypes.REPLACE]
for key in ["event_id", "sender", "origin_server_ts"]:
self.assertIn(key, m_replace_dict)
# We expect that the edit relation appears in the unsigned relations
# section.
relations_dict = result_event_dict["unsigned"].get("m.relations")
self.assertIn(RelationTypes.REPLACE, relations_dict, desc)

self.assert_dict(
{"event_id": edit_event_id, "sender": self.user_id}, m_replace_dict
)
m_replace_dict = relations_dict[RelationTypes.REPLACE]
for key in ["event_id", "sender", "origin_server_ts"]:
self.assertIn(key, m_replace_dict, desc)

self.assert_dict(
{"event_id": edit_event_id, "sender": self.user_id}, m_replace_dict
)

def test_edit_thread(self) -> None:
"""Test that editing a thread works."""
Expand Down Expand Up @@ -605,19 +625,31 @@ def test_edit_edit(self) -> None:
)

# Request the original event.
# /event should return the original event.
channel = self.make_request(
"GET",
f"/rooms/{self.room}/event/{self.parent_id}",
access_token=self.user_token,
)
self.assertEqual(200, channel.code, channel.json_body)
# The edit to the edit should be ignored.
self.assertEqual(channel.json_body["content"], new_body)
self.assertEqual(
channel.json_body["content"], {"body": "Hi!", "msgtype": "m.text"}
)

# The relations information should not include the edit to the edit.
relations_dict = channel.json_body["unsigned"].get("m.relations")
self.assertIn(RelationTypes.REPLACE, relations_dict)

# /context should return the event updated for the *first* edit
# (The edit to the edit should be ignored.)
channel = self.make_request(
"GET",
f"/rooms/{self.room}/context/{self.parent_id}",
access_token=self.user_token,
)
self.assertEqual(200, channel.code, channel.json_body)
self.assertEqual(channel.json_body["event"]["content"], new_body)

m_replace_dict = relations_dict[RelationTypes.REPLACE]
for key in ["event_id", "sender", "origin_server_ts"]:
self.assertIn(key, m_replace_dict)
Expand Down