diff --git a/synapse/api/room_versions.py b/synapse/api/room_versions.py index a747a4081497..c274b8c78307 100644 --- a/synapse/api/room_versions.py +++ b/synapse/api/room_versions.py @@ -68,6 +68,8 @@ class RoomVersion: limit_notifications_power_levels: bool # MSC2174/MSC2176: Apply updated redaction rules algorithm. msc2176_redaction_rules: bool + # MSC2174: Move `redacts` into `content` + msc2174_redacts_key_content: bool # MSC3083: Support the 'restricted' join_rule. msc3083_join_rules: bool # MSC3375: Support for the proper redaction rules for MSC3083. This mustn't @@ -94,6 +96,7 @@ class RoomVersions: strict_canonicaljson=False, limit_notifications_power_levels=False, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=False, @@ -110,6 +113,7 @@ class RoomVersions: strict_canonicaljson=False, limit_notifications_power_levels=False, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=False, @@ -126,6 +130,7 @@ class RoomVersions: strict_canonicaljson=False, limit_notifications_power_levels=False, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=False, @@ -142,6 +147,7 @@ class RoomVersions: strict_canonicaljson=False, limit_notifications_power_levels=False, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=False, @@ -158,6 +164,7 @@ class RoomVersions: strict_canonicaljson=False, limit_notifications_power_levels=False, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=False, @@ -174,6 +181,7 @@ class RoomVersions: strict_canonicaljson=True, limit_notifications_power_levels=True, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=False, @@ -190,6 +198,25 @@ class RoomVersions: strict_canonicaljson=True, limit_notifications_power_levels=True, msc2176_redaction_rules=True, + msc2174_redacts_key_content=False, + msc3083_join_rules=False, + msc3375_redaction_rules=False, + msc2403_knocking=False, + msc2716_historical=False, + msc2716_redactions=False, + ) + MSC2174 = RoomVersion( + # v6 + MSC2174 + "org.matrix.msc2174", + RoomDisposition.UNSTABLE, + EventFormatVersions.V3, + StateResolutionVersions.V2, + enforce_key_validity=True, + special_case_aliases_auth=False, + strict_canonicaljson=True, + limit_notifications_power_levels=True, + msc2176_redaction_rules=False, + msc2174_redacts_key_content=True, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=False, @@ -206,6 +233,7 @@ class RoomVersions: strict_canonicaljson=True, limit_notifications_power_levels=True, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=True, @@ -222,6 +250,7 @@ class RoomVersions: strict_canonicaljson=True, limit_notifications_power_levels=True, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=True, msc3375_redaction_rules=False, msc2403_knocking=True, @@ -238,6 +267,7 @@ class RoomVersions: strict_canonicaljson=True, limit_notifications_power_levels=True, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=True, msc3375_redaction_rules=True, msc2403_knocking=True, @@ -254,6 +284,7 @@ class RoomVersions: strict_canonicaljson=True, limit_notifications_power_levels=True, msc2176_redaction_rules=False, + msc2174_redacts_key_content=False, msc3083_join_rules=False, msc3375_redaction_rules=False, msc2403_knocking=True, @@ -272,6 +303,7 @@ class RoomVersions: RoomVersions.V5, RoomVersions.V6, RoomVersions.MSC2176, + RoomVersions.MSC2174, RoomVersions.V7, RoomVersions.V8, RoomVersions.V9, diff --git a/synapse/event_auth.py b/synapse/event_auth.py index 621a3efcccec..a738c42373bf 100644 --- a/synapse/event_auth.py +++ b/synapse/event_auth.py @@ -579,6 +579,8 @@ def check_redaction( if room_version_obj.event_format == EventFormatVersions.V1: redacter_domain = get_domain_from_id(event.event_id) + # Note: Room versions with v1-formatted events *cannot* have the + # `redacts` key in `content` from MSC2174/MSC2176. if not isinstance(event.redacts, str): return False redactee_domain = get_domain_from_id(event.redacts) diff --git a/synapse/events/utils.py b/synapse/events/utils.py index 026dcde8d83f..50fb69e58a11 100644 --- a/synapse/events/utils.py +++ b/synapse/events/utils.py @@ -386,6 +386,15 @@ def serialize_event( raise TypeError("only_event_fields must be a list of strings") d = only_fields(d, only_event_fields) + if d["content"] is not None and e.type == EventTypes.Redaction: + # If the redacts key is in the `content`, copy it to the top level for + # older clients. Similarly, if the redacts key is already at the top + # level then copy it to `content` for newer clients. + if e.room_version.msc2174_redacts_key_content: + d["redacts"] = d["content"]["redacts"] + else: + d["content"]["redacts"] = d["redacts"] + return d diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 95a89ac01f4b..bd1e3dd8fd6d 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1403,10 +1403,14 @@ async def persist_and_notify_client_event( # user is actually admin or not). is_admin_redaction = False if event.type == EventTypes.Redaction: - assert event.redacts is not None + redacts = event.redacts + if event.room_version.msc2174_redacts_key_content: + redacts = event.content["redacts"] + + assert redacts is not None original_event = await self.store.get_event( - event.redacts, + redacts, redact_behaviour=EventRedactBehaviour.as_is, get_prev_content=False, allow_rejected=False, @@ -1500,10 +1504,13 @@ async def persist_and_notify_client_event( ) if event.type == EventTypes.Redaction: - assert event.redacts is not None + redacts = event.redacts + if event.room_version.msc2174_redacts_key_content: + redacts = event.content["redacts"] + assert redacts is not None original_event = await self.store.get_event( - event.redacts, + redacts, redact_behaviour=EventRedactBehaviour.as_is, get_prev_content=False, allow_rejected=False, @@ -1560,7 +1567,7 @@ async def persist_and_notify_client_event( # checks on the original event. Let's start by checking the original # event exists. if not original_event: - raise NotFoundError("Could not find event %s" % (event.redacts,)) + raise NotFoundError("Could not find event %s" % (redacts,)) if event.user_id != original_event.user_id: raise AuthError(403, "You don't have permission to redact events") diff --git a/synapse/storage/databases/main/cache.py b/synapse/storage/databases/main/cache.py index dd4e83a2ad19..dc6f87c48f09 100644 --- a/synapse/storage/databases/main/cache.py +++ b/synapse/storage/databases/main/cache.py @@ -125,6 +125,7 @@ def process_replication_rows( row.room_id, row.type, row.state_key, + # TODO: Support `redacts` being in `content` rather than at the top level. row.redacts, row.relates_to, backfilled=True, @@ -159,6 +160,7 @@ def _process_event_stream_row(self, token: int, row: EventsStreamRow) -> None: data.room_id, data.type, data.state_key, + # TODO: Support `redacts` being in `content` rather than at the top level. data.redacts, data.relates_to, backfilled=False, diff --git a/synapse/storage/databases/main/events.py b/synapse/storage/databases/main/events.py index 9a6c2fd47a55..9eb0662a8331 100644 --- a/synapse/storage/databases/main/events.py +++ b/synapse/storage/databases/main/events.py @@ -1538,15 +1538,18 @@ def _update_metadata_tables_txn( return for event, _ in events_and_contexts: - if event.type == EventTypes.Redaction and event.redacts is not None: + redacts = event.redacts + if event.room_version.msc2174_redacts_key_content: + redacts = event.content["redacts"] + if event.type == EventTypes.Redaction and redacts is not None: # Remove the entries in the event_push_actions table for the # redacted event. self._remove_push_actions_for_event_id_txn( - txn, event.room_id, event.redacts + txn, event.room_id, redacts ) # Remove from relations table. - self._handle_redact_relations(txn, event.redacts) + self._handle_redact_relations(txn, redacts) # Update the event_forward_extremities, event_backward_extremities and # event_edges tables. @@ -1564,9 +1567,13 @@ def _update_metadata_tables_txn( elif event.type == EventTypes.Message: # Insert into the event_search table. self._store_room_message_txn(txn, event) - elif event.type == EventTypes.Redaction and event.redacts is not None: - # Insert into the redactions table. - self._store_redaction(txn, event) + elif event.type == EventTypes.Redaction: + redacts = event.redacts + if event.room_version.msc2174_redacts_key_content: + redacts = event.content["redacts"] + if redacts is not None: + # Insert into the redactions table. + self._store_redaction(txn, event) elif event.type == EventTypes.Retention: # Update the room_retention table. self._store_retention_policy_for_room_txn(txn, event) @@ -1617,6 +1624,7 @@ def _add_to_cache(self, txn, events_and_contexts): if not ev_map: return + # TODO: Support `redacts` being in `content` rather than at the top level. sql = ( "SELECT " " e.event_id as event_id, " @@ -1648,18 +1656,22 @@ def prefill(): txn.call_after(prefill) def _store_redaction(self, txn: LoggingTransaction, event: EventBase) -> None: + redacts = event.redacts + if event.room_version.msc2174_redacts_key_content: + redacts = event.content["redacts"] + # Invalidate the caches for the redacted event, note that these caches # are also cleared as part of event replication in _invalidate_caches_for_event. - txn.call_after(self.store._invalidate_get_event_cache, event.redacts) - txn.call_after(self.store.get_relations_for_event.invalidate, (event.redacts,)) - txn.call_after(self.store.get_applicable_edit.invalidate, (event.redacts,)) + txn.call_after(self.store._invalidate_get_event_cache, redacts) + txn.call_after(self.store.get_relations_for_event.invalidate, (redacts,)) + txn.call_after(self.store.get_applicable_edit.invalidate, (redacts,)) self.db_pool.simple_upsert_txn( txn, table="redactions", keyvalues={"event_id": event.event_id}, values={ - "redacts": event.redacts, + "redacts": redacts, "received_ts": self._clock.time_msec(), }, ) diff --git a/synapse/storage/databases/main/events_bg_updates.py b/synapse/storage/databases/main/events_bg_updates.py index d5f005966597..c38c5d5a3eee 100644 --- a/synapse/storage/databases/main/events_bg_updates.py +++ b/synapse/storage/databases/main/events_bg_updates.py @@ -635,6 +635,7 @@ async def _event_fix_redactions_bytes( def _event_fix_redactions_bytes_txn(txn: LoggingTransaction) -> None: # This update is quite fast due to new index. + # TODO: Support `redacts` being in `content` rather than at the top level. txn.execute( """ UPDATE event_json diff --git a/synapse/storage/databases/main/events_worker.py b/synapse/storage/databases/main/events_worker.py index a4a604a49915..4a2f12f67955 100644 --- a/synapse/storage/databases/main/events_worker.py +++ b/synapse/storage/databases/main/events_worker.py @@ -497,7 +497,10 @@ async def get_events_as_list( # we have to recheck auth now. if not allow_rejected and entry.event.type == EventTypes.Redaction: - if entry.event.redacts is None: + redacted_event_id = entry.event.redacts + if entry.event.room_version.msc2174_redacts_key_content: + redacted_event_id = entry.event.content["redacts"] + if redacted_event_id is None: # A redacted redaction doesn't have a `redacts` key, in # which case lets just withhold the event. # @@ -511,7 +514,6 @@ async def get_events_as_list( ) continue - redacted_event_id = entry.event.redacts event_map = await self._get_events_from_cache_or_db([redacted_event_id]) original_event_entry = event_map.get(redacted_event_id) if not original_event_entry: