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

Commit 493488d

Browse files
committed
Implement MSC3912: Relation-based redactions
1 parent 04d7f56 commit 493488d

File tree

5 files changed

+175
-24
lines changed

5 files changed

+175
-24
lines changed

synapse/config/experimental.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,6 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
125125
self.msc3886_endpoint: Optional[str] = experimental.get(
126126
"msc3886_endpoint", None
127127
)
128+
129+
# MSC3912: Relation-based redactions.
130+
self.msc3912_enabled: bool = experimental.get("msc3912_enabled", False)

synapse/handlers/message.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,36 @@ async def deduplicate_state_event(
877877
return prev_event
878878
return None
879879

880+
async def get_event_from_transaction(
881+
self,
882+
requester: Requester,
883+
txn_id: str,
884+
room_id: str,
885+
) -> Optional[EventBase]:
886+
"""For the given transaction ID and room ID, check if there is a matching event.
887+
If so, fetch it and return it.
888+
889+
Args:
890+
requester: The requester making the request in the context of which we want
891+
to fetch the event.
892+
txn_id: The transaction ID.
893+
room_id: The room ID.
894+
895+
Returns:
896+
An event if one could be found, None otherwise.
897+
"""
898+
if requester.access_token_id:
899+
existing_event_id = await self.store.get_event_id_from_transaction_id(
900+
room_id,
901+
requester.user.to_string(),
902+
requester.access_token_id,
903+
txn_id,
904+
)
905+
if existing_event_id:
906+
return await self.store.get_event(existing_event_id)
907+
908+
return None
909+
880910
async def create_and_send_nonmember_event(
881911
self,
882912
requester: Requester,
@@ -956,18 +986,17 @@ async def create_and_send_nonmember_event(
956986
# extremities to pile up, which in turn leads to state resolution
957987
# taking longer.
958988
async with self.limiter.queue(event_dict["room_id"]):
959-
if txn_id and requester.access_token_id:
960-
existing_event_id = await self.store.get_event_id_from_transaction_id(
961-
event_dict["room_id"],
962-
requester.user.to_string(),
963-
requester.access_token_id,
964-
txn_id,
989+
if txn_id:
990+
event = await self.get_event_from_transaction(
991+
requester, txn_id, event_dict["room_id"]
965992
)
966-
if existing_event_id:
967-
event = await self.store.get_event(existing_event_id)
993+
if event:
968994
# we know it was persisted, so must have a stream ordering
969995
assert event.internal_metadata.stream_ordering
970-
return event, event.internal_metadata.stream_ordering
996+
return (
997+
event,
998+
event.internal_metadata.stream_ordering,
999+
)
9711000

9721001
event, context = await self.create_event(
9731002
requester,

synapse/handlers/relations.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import attr
1919

20-
from synapse.api.constants import RelationTypes
20+
from synapse.api.constants import EventTypes, RelationTypes
2121
from synapse.api.errors import SynapseError
2222
from synapse.events import EventBase, relation_from_event
2323
from synapse.logging.opentracing import trace
@@ -75,6 +75,7 @@ def __init__(self, hs: "HomeServer"):
7575
self._clock = hs.get_clock()
7676
self._event_handler = hs.get_event_handler()
7777
self._event_serializer = hs.get_event_client_serializer()
78+
self._event_creation_handler = hs.get_event_creation_handler()
7879

7980
async def get_relations(
8081
self,
@@ -205,6 +206,59 @@ async def get_relations_for_event(
205206

206207
return related_events, next_token
207208

209+
async def redact_events_related_to(
210+
self,
211+
requester: Requester,
212+
event_id: str,
213+
initial_redaction_event: EventBase,
214+
relation_types: List[str],
215+
) -> None:
216+
"""Redacts all events related to the given event ID with one of the given
217+
relation types.
218+
219+
This method is expected to be called when redacting the event referred to by
220+
the given event ID.
221+
222+
If an event cannot be redacted (e.g. because of insufficient permissions), log
223+
the error and try to redact the next one.
224+
225+
Args:
226+
requester: The requester to redact events on behalf of.
227+
event_id: The event IDs to look and redact relations of.
228+
initial_redaction_event: The redaction for the event referred to by
229+
event_id.
230+
relation_types: The types of relations to look for.
231+
232+
Raises:
233+
ShadowBanError if the requester is shadow-banned
234+
"""
235+
related_event_ids = (
236+
await self._main_store.get_all_relations_for_event_with_types(
237+
event_id, relation_types
238+
)
239+
)
240+
241+
for related_event_id in related_event_ids:
242+
try:
243+
await self._event_creation_handler.create_and_send_nonmember_event(
244+
requester,
245+
{
246+
"type": EventTypes.Redaction,
247+
"content": initial_redaction_event.content,
248+
"room_id": initial_redaction_event.room_id,
249+
"sender": requester.user.to_string(),
250+
"redacts": related_event_id,
251+
},
252+
ratelimit=False,
253+
)
254+
except SynapseError as e:
255+
logger.warning(
256+
"Failed to redact event %s (related to event %s): %s",
257+
related_event_id,
258+
event_id,
259+
e.msg,
260+
)
261+
208262
async def get_annotations_for_event(
209263
self,
210264
event_id: str,

synapse/rest/client/room.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,8 @@ def __init__(self, hs: "HomeServer"):
10291029
super().__init__(hs)
10301030
self.event_creation_handler = hs.get_event_creation_handler()
10311031
self.auth = hs.get_auth()
1032+
self._relation_handler = hs.get_relations_handler()
1033+
self._msc3912_enabled = hs.config.experimental.msc3912_enabled
10321034

10331035
def register(self, http_server: HttpServer) -> None:
10341036
PATTERNS = "/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)"
@@ -1045,20 +1047,45 @@ async def on_POST(
10451047
content = parse_json_object_from_request(request)
10461048

10471049
try:
1048-
(
1049-
event,
1050-
_,
1051-
) = await self.event_creation_handler.create_and_send_nonmember_event(
1052-
requester,
1053-
{
1054-
"type": EventTypes.Redaction,
1055-
"content": content,
1056-
"room_id": room_id,
1057-
"sender": requester.user.to_string(),
1058-
"redacts": event_id,
1059-
},
1060-
txn_id=txn_id,
1061-
)
1050+
with_relations = None
1051+
if self._msc3912_enabled and "org.matrix.msc3912.with_relations" in content:
1052+
with_relations = content["org.matrix.msc3912.with_relations"]
1053+
del content["org.matrix.msc3912.with_relations"]
1054+
1055+
# Check if there's an existing event for this transaction now (even though
1056+
# create_and_send_nonmember_event also does it) because, if there's one,
1057+
# then we want to skip the call to redact_events_related_to.
1058+
event = None
1059+
if txn_id:
1060+
event = await self.event_creation_handler.get_event_from_transaction(
1061+
requester, txn_id, room_id
1062+
)
1063+
1064+
if event is None:
1065+
(
1066+
event,
1067+
_,
1068+
) = await self.event_creation_handler.create_and_send_nonmember_event(
1069+
requester,
1070+
{
1071+
"type": EventTypes.Redaction,
1072+
"content": content,
1073+
"room_id": room_id,
1074+
"sender": requester.user.to_string(),
1075+
"redacts": event_id,
1076+
},
1077+
txn_id=txn_id,
1078+
)
1079+
1080+
if with_relations:
1081+
run_in_background(
1082+
self._relation_handler.redact_events_related_to,
1083+
requester=requester,
1084+
event_id=event_id,
1085+
initial_redaction_event=event,
1086+
relation_types=with_relations,
1087+
)
1088+
10621089
event_id = event.event_id
10631090
except ShadowBanError:
10641091
event_id = "$" + random_string(43)

synapse/storage/databases/main/relations.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,44 @@ def _get_recent_references_for_event_txn(
295295
"get_recent_references_for_event", _get_recent_references_for_event_txn
296296
)
297297

298+
async def get_all_relations_for_event_with_types(
299+
self,
300+
event_id: str,
301+
relation_types: List[str],
302+
) -> List[str]:
303+
"""Get the event IDs of all events that have a relation to the given event with
304+
one of the given relation types.
305+
306+
Args:
307+
event_id: The event for which to look for related events.
308+
relation_types: The types of relations to look for.
309+
310+
Returns:
311+
A list of the IDs of the events that relate to the given event with one of
312+
the given relation types.
313+
"""
314+
315+
def get_all_relation_ids_for_event_with_types_txn(
316+
txn: LoggingTransaction,
317+
) -> List[str]:
318+
sql = """
319+
SELECT event_id FROM event_relations
320+
WHERE
321+
relates_to_id = ?
322+
AND relation_type IN (%s)
323+
""" % (
324+
",".join(["?" for _ in relation_types]),
325+
)
326+
327+
txn.execute(sql, [event_id] + relation_types)
328+
329+
return [row[0] for row in txn]
330+
331+
return await self.db_pool.runInteraction(
332+
desc="get_all_relation_ids_for_event_with_types",
333+
func=get_all_relation_ids_for_event_with_types_txn,
334+
)
335+
298336
async def event_includes_relation(self, event_id: str) -> bool:
299337
"""Check if the given event relates to another event.
300338

0 commit comments

Comments
 (0)