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:
parent
c9723a1c1f
commit
2503126d52
8 changed files with 87 additions and 17 deletions
1
changelog.d/15395.misc
Normal file
1
changelog.d/15395.misc
Normal 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.
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue