0
0
Fork 1
mirror of https://mau.dev/maunium/synapse.git synced 2024-12-14 14:03:54 +01:00

Implement MSC2174: move redacts to a content property. (#15395)

This moves `redacts` from being a top-level property to
a `content` property in a new room version.

MSC2176 (which was previously implemented) states to not
`redact` this property.
This commit is contained in:
Patrick Cloke 2023-04-13 09:47:07 -04:00 committed by GitHub
parent c9723a1c1f
commit 2503126d52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 17 deletions

1
changelog.d/15395.misc Normal file
View file

@ -0,0 +1 @@
Implement [MSC2174](https://github.com/matrix-org/matrix-spec-proposals/pull/2174) to move the `redacts` key to a `content` property.

View file

@ -80,7 +80,8 @@ class RoomVersion:
limit_notifications_power_levels: bool limit_notifications_power_levels: bool
# MSC2175: No longer include the creator in m.room.create events. # MSC2175: No longer include the creator in m.room.create events.
msc2175_implicit_room_creator: bool msc2175_implicit_room_creator: bool
# MSC2174/MSC2176: Apply updated redaction rules algorithm. # MSC2174/MSC2176: Apply updated redaction rules algorithm, move redacts to
# content property.
msc2176_redaction_rules: bool msc2176_redaction_rules: bool
# MSC3083: Support the 'restricted' join_rule. # MSC3083: Support the 'restricted' join_rule.
msc3083_join_rules: bool msc3083_join_rules: bool

View file

@ -793,7 +793,7 @@ def check_redaction(
"""Check whether the event sender is allowed to redact the target event. """Check whether the event sender is allowed to redact the target event.
Returns: Returns:
True if the the sender is allowed to redact the target event if the True if the sender is allowed to redact the target event if the
target event was created by them. target event was created by them.
False if the sender is allowed to redact the target event with no False if the sender is allowed to redact the target event with no
further checks. further checks.

View file

@ -326,7 +326,6 @@ class EventBase(metaclass=abc.ABCMeta):
hashes: DictProperty[Dict[str, str]] = DictProperty("hashes") hashes: DictProperty[Dict[str, str]] = DictProperty("hashes")
origin: DictProperty[str] = DictProperty("origin") origin: DictProperty[str] = DictProperty("origin")
origin_server_ts: DictProperty[int] = DictProperty("origin_server_ts") origin_server_ts: DictProperty[int] = DictProperty("origin_server_ts")
redacts: DefaultDictProperty[Optional[str]] = DefaultDictProperty("redacts", None)
room_id: DictProperty[str] = DictProperty("room_id") room_id: DictProperty[str] = DictProperty("room_id")
sender: DictProperty[str] = DictProperty("sender") sender: DictProperty[str] = DictProperty("sender")
# TODO state_key should be Optional[str]. This is generally asserted in Synapse # TODO state_key should be Optional[str]. This is generally asserted in Synapse
@ -346,6 +345,13 @@ class EventBase(metaclass=abc.ABCMeta):
def membership(self) -> str: def membership(self) -> str:
return self.content["membership"] return self.content["membership"]
@property
def redacts(self) -> Optional[str]:
"""MSC2176 moved the redacts field into the content."""
if self.room_version.msc2176_redaction_rules:
return self.content.get("redacts")
return self.get("redacts")
def is_state(self) -> bool: def is_state(self) -> bool:
return self.get_state_key() is not None return self.get_state_key() is not None

View file

@ -173,7 +173,9 @@ class EventBuilder:
if self.is_state(): if self.is_state():
event_dict["state_key"] = self._state_key event_dict["state_key"] = self._state_key
if self._redacts is not None: # MSC2174 moves the redacts property to the content, it is invalid to
# provide it as a top-level property.
if self._redacts is not None and not self.room_version.msc2176_redaction_rules:
event_dict["redacts"] = self._redacts event_dict["redacts"] = self._redacts
if self._origin_server_ts is not None: if self._origin_server_ts is not None:

View file

@ -1096,6 +1096,7 @@ class RoomRedactEventRestServlet(TransactionRestServlet):
super().__init__(hs) super().__init__(hs)
self.event_creation_handler = hs.get_event_creation_handler() self.event_creation_handler = hs.get_event_creation_handler()
self.auth = hs.get_auth() self.auth = hs.get_auth()
self._store = hs.get_datastores().main
self._relation_handler = hs.get_relations_handler() self._relation_handler = hs.get_relations_handler()
self._msc3912_enabled = hs.config.experimental.msc3912_enabled self._msc3912_enabled = hs.config.experimental.msc3912_enabled
@ -1113,6 +1114,19 @@ class RoomRedactEventRestServlet(TransactionRestServlet):
) -> Tuple[int, JsonDict]: ) -> Tuple[int, JsonDict]:
content = parse_json_object_from_request(request) content = parse_json_object_from_request(request)
# Ensure the redacts property in the content matches the one provided in
# the URL.
room_version = await self._store.get_room_version(room_id)
if room_version.msc2176_redaction_rules:
if "redacts" in content and content["redacts"] != event_id:
raise SynapseError(
400,
"Cannot provide a redacts value incoherent with the event_id of the URL parameter",
Codes.INVALID_PARAM,
)
else:
content["redacts"] = event_id
try: try:
with_relations = None with_relations = None
if self._msc3912_enabled and "org.matrix.msc3912.with_relations" in content: if self._msc3912_enabled and "org.matrix.msc3912.with_relations" in content:
@ -1128,20 +1142,23 @@ class RoomRedactEventRestServlet(TransactionRestServlet):
requester, txn_id, room_id requester, txn_id, room_id
) )
# Event is not yet redacted, create a new event to redact it.
if event is None: if event is None:
event_dict = {
"type": EventTypes.Redaction,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
}
# Earlier room versions had a top-level redacts property.
if not room_version.msc2176_redaction_rules:
event_dict["redacts"] = event_id
( (
event, event,
_, _,
) = await self.event_creation_handler.create_and_send_nonmember_event( ) = await self.event_creation_handler.create_and_send_nonmember_event(
requester, requester, event_dict, txn_id=txn_id
{
"type": EventTypes.Redaction,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
"redacts": event_id,
},
txn_id=txn_id,
) )
if with_relations: if with_relations:

View file

@ -318,7 +318,11 @@ class PruneEventTestCase(stdlib_unittest.TestCase):
"""Redaction events have no special behaviour until MSC2174/MSC2176.""" """Redaction events have no special behaviour until MSC2174/MSC2176."""
self.run_test( self.run_test(
{"type": "m.room.redaction", "content": {"redacts": "$test2:domain"}}, {
"type": "m.room.redaction",
"content": {"redacts": "$test2:domain"},
"redacts": "$test2:domain",
},
{ {
"type": "m.room.redaction", "type": "m.room.redaction",
"content": {}, "content": {},
@ -330,7 +334,11 @@ class PruneEventTestCase(stdlib_unittest.TestCase):
# After MSC2174, redaction events keep the redacts content key. # After MSC2174, redaction events keep the redacts content key.
self.run_test( self.run_test(
{"type": "m.room.redaction", "content": {"redacts": "$test2:domain"}}, {
"type": "m.room.redaction",
"content": {"redacts": "$test2:domain"},
"redacts": "$test2:domain",
},
{ {
"type": "m.room.redaction", "type": "m.room.redaction",
"content": {"redacts": "$test2:domain"}, "content": {"redacts": "$test2:domain"},

View file

@ -16,6 +16,7 @@ from typing import List, Optional
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.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
@ -74,6 +75,7 @@ class RedactionsTestCase(HomeserverTestCase):
event_id: str, event_id: str,
expect_code: int = 200, expect_code: int = 200,
with_relations: Optional[List[str]] = None, with_relations: Optional[List[str]] = None,
content: Optional[JsonDict] = None,
) -> JsonDict: ) -> JsonDict:
"""Helper function to send a redaction event. """Helper function to send a redaction event.
@ -81,7 +83,7 @@ class RedactionsTestCase(HomeserverTestCase):
""" """
path = "/_matrix/client/r0/rooms/%s/redact/%s" % (room_id, event_id) path = "/_matrix/client/r0/rooms/%s/redact/%s" % (room_id, event_id)
request_content = {} request_content = content or {}
if with_relations: if with_relations:
request_content["org.matrix.msc3912.with_relations"] = with_relations request_content["org.matrix.msc3912.with_relations"] = with_relations
@ -92,7 +94,7 @@ class RedactionsTestCase(HomeserverTestCase):
return channel.json_body return channel.json_body
def _sync_room_timeline(self, access_token: str, room_id: str) -> List[JsonDict]: def _sync_room_timeline(self, access_token: str, room_id: str) -> List[JsonDict]:
channel = self.make_request("GET", "sync", access_token=self.mod_access_token) channel = self.make_request("GET", "sync", access_token=access_token)
self.assertEqual(channel.code, 200) self.assertEqual(channel.code, 200)
room_sync = channel.json_body["rooms"]["join"][room_id] room_sync = channel.json_body["rooms"]["join"][room_id]
return room_sync["timeline"]["events"] return room_sync["timeline"]["events"]
@ -466,3 +468,36 @@ 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:
"""MSC2174 moved the redacts property to the content."""
# Create a room with the newer room version.
room_id = self.helper.create_room_as(
self.mod_user_id,
tok=self.mod_access_token,
room_version=RoomVersions.MSC2176.identifier,
)
# Create an event.
b = self.helper.send(room_id=room_id, tok=self.mod_access_token)
event_id = b["event_id"]
# Attempt to redact it with a bogus event ID.
self._redact_event(
self.mod_access_token,
room_id,
event_id,
expect_code=400,
content={"redacts": "foo"},
)
# 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
timeline = self._sync_room_timeline(self.mod_access_token, room_id)
redact_event = timeline[-1]
self.assertEqual(redact_event["type"], EventTypes.Redaction)
# The redacts key should be in the content.
self.assertNotIn("redacts", redact_event)
self.assertEquals(redact_event["content"]["redacts"], event_id)