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

Commit

Permalink
Implement MSC3912: Relation-based redactions
Browse files Browse the repository at this point in the history
  • Loading branch information
babolivier committed Oct 21, 2022
1 parent 04d7f56 commit 06caa97
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 24 deletions.
3 changes: 3 additions & 0 deletions synapse/config/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,6 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
self.msc3886_endpoint: Optional[str] = experimental.get(
"msc3886_endpoint", None
)

# MSC3912: Relation-based redactions.
self.msc3912_enabled: bool = experimental.get("msc3912_enabled", False)
47 changes: 38 additions & 9 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,36 @@ async def deduplicate_state_event(
return prev_event
return None

async def get_event_from_transaction(
self,
requester: Requester,
txn_id: str,
room_id: str,
) -> Optional[EventBase]:
"""For the given transaction ID and room ID, check if there is a matching event.
If so, fetch it and return it.
Args:
requester: The requester making the request in the context of which we want
to fetch the event.
txn_id: The transaction ID.
room_id: The room ID.
Returns:
An event if one could be found, None otherwise.
"""
if requester.access_token_id:
existing_event_id = await self.store.get_event_id_from_transaction_id(
room_id,
requester.user.to_string(),
requester.access_token_id,
txn_id,
)
if existing_event_id:
return await self.store.get_event(existing_event_id)

return None

async def create_and_send_nonmember_event(
self,
requester: Requester,
Expand Down Expand Up @@ -956,18 +986,17 @@ async def create_and_send_nonmember_event(
# extremities to pile up, which in turn leads to state resolution
# taking longer.
async with self.limiter.queue(event_dict["room_id"]):
if txn_id and requester.access_token_id:
existing_event_id = await self.store.get_event_id_from_transaction_id(
event_dict["room_id"],
requester.user.to_string(),
requester.access_token_id,
txn_id,
if txn_id:
event = await self.get_event_from_transaction(
requester, txn_id, event_dict["room_id"]
)
if existing_event_id:
event = await self.store.get_event(existing_event_id)
if event:
# we know it was persisted, so must have a stream ordering
assert event.internal_metadata.stream_ordering
return event, event.internal_metadata.stream_ordering
return (
event,
event.internal_metadata.stream_ordering,
)

event, context = await self.create_event(
requester,
Expand Down
37 changes: 36 additions & 1 deletion synapse/handlers/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import attr

from synapse.api.constants import RelationTypes
from synapse.api.constants import EventTypes, RelationTypes
from synapse.api.errors import SynapseError
from synapse.events import EventBase, relation_from_event
from synapse.logging.opentracing import trace
Expand Down Expand Up @@ -75,6 +75,7 @@ def __init__(self, hs: "HomeServer"):
self._clock = hs.get_clock()
self._event_handler = hs.get_event_handler()
self._event_serializer = hs.get_event_client_serializer()
self._event_creation_handler = hs.get_event_creation_handler()

async def get_relations(
self,
Expand Down Expand Up @@ -205,6 +206,40 @@ async def get_relations_for_event(

return related_events, next_token

async def redact_events_related_to(
self,
requester: Requester,
event_id: str,
initial_redaction_event: EventBase,
relation_types: List[str],
) -> None:
related_event_ids = (
await self._main_store.get_all_relations_for_event_with_types(
event_id, relation_types
)
)

for related_event_id in related_event_ids:
try:
await self._event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.Redaction,
"content": initial_redaction_event.content,
"room_id": initial_redaction_event.room_id,
"sender": requester.user.to_string(),
"redacts": related_event_id,
},
ratelimit=False,
)
except SynapseError as e:
logger.warning(
"Failed to redact event %s (related to event %s): %s",
related_event_id,
event_id,
e.msg,
)

async def get_annotations_for_event(
self,
event_id: str,
Expand Down
55 changes: 41 additions & 14 deletions synapse/rest/client/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,8 @@ def __init__(self, hs: "HomeServer"):
super().__init__(hs)
self.event_creation_handler = hs.get_event_creation_handler()
self.auth = hs.get_auth()
self._relation_handler = hs.get_relations_handler()
self._msc3912_enabled = hs.config.experimental.msc3912_enabled

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

try:
(
event,
_,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.Redaction,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
"redacts": event_id,
},
txn_id=txn_id,
)
with_relations = None
if self._msc3912_enabled and "org.matrix.msc3912.with_relations" in content:
with_relations = content["org.matrix.msc3912.with_relations"]
del content["org.matrix.msc3912.with_relations"]

# Check if there's an existing event for this transaction now (even though
# create_and_send_nonmember_event also does it) because, if there's one,
# then we want to skip the call to redact_events_related_to.
event = None
if txn_id:
event = await self.event_creation_handler.get_event_from_transaction(
requester, txn_id, room_id
)

if event is None:
(
event,
_,
) = await self.event_creation_handler.create_and_send_nonmember_event(
requester,
{
"type": EventTypes.Redaction,
"content": content,
"room_id": room_id,
"sender": requester.user.to_string(),
"redacts": event_id,
},
txn_id=txn_id,
)

if with_relations:
run_in_background(
self._relation_handler.redact_events_related_to,
requester=requester,
event_id=event_id,
initial_redaction_event=event,
relation_types=with_relations,
)

event_id = event.event_id
except ShadowBanError:
event_id = "$" + random_string(43)
Expand Down
38 changes: 38 additions & 0 deletions synapse/storage/databases/main/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,44 @@ def _get_recent_references_for_event_txn(
"get_recent_references_for_event", _get_recent_references_for_event_txn
)

async def get_all_relations_for_event_with_types(
self,
event_id: str,
relation_types: List[str],
) -> List[str]:
"""Get the event IDs of all events that have a relation to the given event with
one of the given relation types.
Args:
event_id: The event for which to look for related events.
relation_types: The types of relations to look for.
Returns:
A list of the IDs of the events that relate to the given event with one of
the given relation types.
"""

def get_all_relation_ids_for_event_with_types_txn(
txn: LoggingTransaction,
) -> List[str]:
sql = """
SELECT event_id FROM event_relations
WHERE
relates_to_id = ?
AND relation_type IN (%s)
""" % (
",".join(["?" for _ in relation_types]),
)

txn.execute(sql, [event_id] + relation_types)

return [row[0] for row in txn]

return await self.db_pool.runInteraction(
desc="get_all_relation_ids_for_event_with_types",
func=get_all_relation_ids_for_event_with_types_txn,
)

async def event_includes_relation(self, event_id: str) -> bool:
"""Check if the given event relates to another event.
Expand Down

0 comments on commit 06caa97

Please sign in to comment.