Add forward-compatibility for the redacts property (MSC2174). (#16013)

The location of the redacts field changes in room version 11. Ensure
it is copied to the *new* location for *old* room versions for
forwards-compatibility with clients.

Note that copying it to the *old* location for the *new* room version
was previously handled.
This commit is contained in:
Patrick Cloke 2023-08-02 11:35:54 -04:00 committed by GitHub
parent 01a45869f0
commit 4f5bccbbba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 25 deletions

1
changelog.d/16013.misc Normal file
View File

@ -0,0 +1 @@
Properly overwrite the `redacts` content-property for forwards-compatibility with room versions 1 through 10.

View File

@ -475,14 +475,16 @@ def serialize_event(
if config.as_client_event: if config.as_client_event:
d = config.event_format(d) d = config.event_format(d)
# If the event is a redaction, copy the redacts field from the content to # If the event is a redaction, the field with the redacted event ID appears
# top-level for backwards compatibility. # in a different location depending on the room version. e.redacts handles
if ( # fetching from the proper location; copy it to the other location for forwards-
e.type == EventTypes.Redaction # and backwards-compatibility with clients.
and e.room_version.updated_redaction_rules if e.type == EventTypes.Redaction and e.redacts is not None:
and e.redacts is not None if e.room_version.updated_redaction_rules:
): d["redacts"] = e.redacts
d["redacts"] = e.redacts else:
d["content"] = dict(d["content"])
d["content"]["redacts"] = e.redacts
only_event_fields = config.only_event_fields only_event_fields = config.only_event_fields
if only_event_fields: if only_event_fields:

View File

@ -13,10 +13,12 @@
# limitations under the License. # limitations under the License.
from typing import List, Optional from typing import List, Optional
from parameterized import parameterized
from twisted.test.proto_helpers import MemoryReactor from twisted.test.proto_helpers import MemoryReactor
from synapse.api.constants import EventTypes, RelationTypes from synapse.api.constants import EventTypes, RelationTypes
from synapse.api.room_versions import RoomVersions from synapse.api.room_versions import RoomVersion, RoomVersions
from synapse.rest import admin from synapse.rest import admin
from synapse.rest.client import login, room, sync from synapse.rest.client import login, room, sync
from synapse.server import HomeServer from synapse.server import HomeServer
@ -569,50 +571,81 @@ class RedactionsTestCase(HomeserverTestCase):
self.assertIn("body", event_dict["content"], event_dict) self.assertIn("body", event_dict["content"], event_dict)
self.assertEqual("I'm in a thread!", event_dict["content"]["body"]) self.assertEqual("I'm in a thread!", event_dict["content"]["body"])
def test_content_redaction(self) -> None: @parameterized.expand(
"""MSC2174 moved the redacts property to the content.""" [
# Tuples of:
# Room version
# Boolean: True if the redaction event content should include the event ID.
# Boolean: true if the resulting redaction event is expected to include the
# event ID in the content.
(RoomVersions.V10, False, False),
(RoomVersions.V11, True, True),
(RoomVersions.V11, False, True),
]
)
def test_redaction_content(
self, room_version: RoomVersion, include_content: bool, expect_content: bool
) -> None:
"""
Room version 11 moved the redacts property to the content.
Ensure that the event gets created properly and that the Client-Server
API servers the proper backwards-compatible version.
"""
# Create a room with the newer room version. # Create a room with the newer room version.
room_id = self.helper.create_room_as( room_id = self.helper.create_room_as(
self.mod_user_id, self.mod_user_id,
tok=self.mod_access_token, tok=self.mod_access_token,
room_version=RoomVersions.V11.identifier, room_version=room_version.identifier,
) )
# Create an event. # Create an event.
b = self.helper.send(room_id=room_id, tok=self.mod_access_token) b = self.helper.send(room_id=room_id, tok=self.mod_access_token)
event_id = b["event_id"] event_id = b["event_id"]
# Attempt to redact it with a bogus event ID. # Ensure the event ID in the URL and the content must match.
self._redact_event( if include_content:
self._redact_event(
self.mod_access_token,
room_id,
event_id,
expect_code=400,
content={"redacts": "foo"},
)
# Redact it for real.
result = self._redact_event(
self.mod_access_token, self.mod_access_token,
room_id, room_id,
event_id, event_id,
expect_code=400, content={"redacts": event_id} if include_content else {},
content={"redacts": "foo"},
) )
redaction_event_id = result["event_id"]
# Redact it for real.
self._redact_event(self.mod_access_token, room_id, event_id)
# Sync the room, to get the id of the create event # Sync the room, to get the id of the create event
timeline = self._sync_room_timeline(self.mod_access_token, room_id) timeline = self._sync_room_timeline(self.mod_access_token, room_id)
redact_event = timeline[-1] redact_event = timeline[-1]
self.assertEqual(redact_event["type"], EventTypes.Redaction) self.assertEqual(redact_event["type"], EventTypes.Redaction)
# The redacts key should be in the content. # The redacts key should be in the content and the redacts keys.
self.assertEquals(redact_event["content"]["redacts"], event_id) self.assertEquals(redact_event["content"]["redacts"], event_id)
# It should also be copied as the top-level redacts field for backwards
# compatibility.
self.assertEquals(redact_event["redacts"], event_id) self.assertEquals(redact_event["redacts"], event_id)
# But it isn't actually part of the event. # But it isn't actually part of the event.
def get_event(txn: LoggingTransaction) -> JsonDict: def get_event(txn: LoggingTransaction) -> JsonDict:
return db_to_json( return db_to_json(
main_datastore._fetch_event_rows(txn, [event_id])[event_id].json main_datastore._fetch_event_rows(txn, [redaction_event_id])[
redaction_event_id
].json
) )
main_datastore = self.hs.get_datastores().main main_datastore = self.hs.get_datastores().main
event_json = self.get_success( event_json = self.get_success(
main_datastore.db_pool.runInteraction("get_event", get_event) main_datastore.db_pool.runInteraction("get_event", get_event)
) )
self.assertNotIn("redacts", event_json) self.assertEquals(event_json["type"], EventTypes.Redaction)
if expect_content:
self.assertNotIn("redacts", event_json)
self.assertEquals(event_json["content"]["redacts"], event_id)
else:
self.assertEquals(event_json["redacts"], event_id)
self.assertNotIn("redacts", event_json["content"])