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

Make 'event.redacts' never raise. #6771

Merged
merged 3 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/6771.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix persisting redaction events that have been redacted (or otherwise don't have a redacts key).
28 changes: 24 additions & 4 deletions synapse/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,32 @@ def is_redacted(self):
return getattr(self, "redacted", False)


def _event_dict_property(key):
_SENTINEL = object()


def _event_dict_property(key, default=_SENTINEL):
"""Creates a new property for the given key that delegates access to
`self._event_dict`.

The default is used if the key is missing from the `_event_dict`, if given,
otherwise an AttributeError will be raised.

Note: If a default is given then `hasattr` will always return true.
"""

# We want to be able to use hasattr with the event dict properties.
# However, (on python3) hasattr expects AttributeError to be raised. Hence,
# we need to transform the KeyError into an AttributeError
def getter(self):

def getter_raises(self):
try:
return self._event_dict[key]
except KeyError:
raise AttributeError(key)

def getter_default(self):
return self._event_dict.get(key, default)

def setter(self, v):
try:
self._event_dict[key] = v
Expand All @@ -138,7 +154,11 @@ def delete(self):
except KeyError:
raise AttributeError(key)

return property(getter, setter, delete)
if default is _SENTINEL:
# No default given, so use the getter that raises
return property(getter_raises, setter, delete)
else:
return property(getter_default, setter, delete)


class EventBase(object):
Expand All @@ -165,7 +185,7 @@ def __init__(
origin = _event_dict_property("origin")
origin_server_ts = _event_dict_property("origin_server_ts")
prev_events = _event_dict_property("prev_events")
redacts = _event_dict_property("redacts")
redacts = _event_dict_property("redacts", None)
room_id = _event_dict_property("room_id")
sender = _event_dict_property("sender")
user_id = _event_dict_property("sender")
Expand Down
2 changes: 1 addition & 1 deletion synapse/storage/data_stores/main/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ 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:
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.Retention:
Expand Down
2 changes: 1 addition & 1 deletion synapse/storage/data_stores/main/events_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def get_events_as_list(
# we have to recheck auth now.

if not allow_rejected and entry.event.type == EventTypes.Redaction:
if not hasattr(entry.event, "redacts"):
if entry.event.redacts is None:
# A redacted redaction doesn't have a `redacts` key, in
# which case lets just withhold the event.
#
Expand Down
35 changes: 35 additions & 0 deletions tests/storage/test_redaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,38 @@ def test_redact_redaction(self):
self.get_success(
self.store.get_event(first_redact_event.event_id, allow_none=True)
)

def test_store_redacted_redaction(self):
"""Tests that we can store a redacted redaction.
"""

self.get_success(
self.inject_room_member(self.room1, self.u_alice, Membership.JOIN)
)

builder = self.event_builder_factory.for_room_version(
RoomVersions.V1,
{
"type": EventTypes.Redaction,
"sender": self.u_alice.to_string(),
"room_id": self.room1.to_string(),
"content": {"reason": "foo"},
},
)

redaction_event, context = self.get_success(
self.event_creation_handler.create_new_client_event(builder)
)

self.get_success(
self.storage.persistence.persist_event(redaction_event, context)
)

# Now lets jump to the future where we have censored the redaction event
# in the DB.
self.reactor.advance(60 * 60 * 24 * 31)

# We just want to check that fetching the event doesn't raise an exception.
self.get_success(
self.store.get_event(redaction_event.event_id, allow_none=True)
)