Make 'event.redacts' never raise. (#6771)

There are quite a few places that we assume that a redaction event has a
corresponding `redacts` key, which is not always the case. So lets
cheekily make it so that event.redacts just returns None instead.
pull/6775/head
Erik Johnston 2020-01-23 15:19:03 +00:00 committed by GitHub
parent 51fc3f693e
commit fa4d609e20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 6 deletions

1
changelog.d/6771.bugfix Normal file
View File

@ -0,0 +1 @@
Fix persisting redaction events that have been redacted (or otherwise don't have a redacts key).

View File

@ -116,16 +116,32 @@ class _EventInternalMetadata(object):
return getattr(self, "redacted", False) 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. # We want to be able to use hasattr with the event dict properties.
# However, (on python3) hasattr expects AttributeError to be raised. Hence, # However, (on python3) hasattr expects AttributeError to be raised. Hence,
# we need to transform the KeyError into an AttributeError # we need to transform the KeyError into an AttributeError
def getter(self):
def getter_raises(self):
try: try:
return self._event_dict[key] return self._event_dict[key]
except KeyError: except KeyError:
raise AttributeError(key) raise AttributeError(key)
def getter_default(self):
return self._event_dict.get(key, default)
def setter(self, v): def setter(self, v):
try: try:
self._event_dict[key] = v self._event_dict[key] = v
@ -138,7 +154,11 @@ def _event_dict_property(key):
except KeyError: except KeyError:
raise AttributeError(key) 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): class EventBase(object):
@ -165,7 +185,7 @@ class EventBase(object):
origin = _event_dict_property("origin") origin = _event_dict_property("origin")
origin_server_ts = _event_dict_property("origin_server_ts") origin_server_ts = _event_dict_property("origin_server_ts")
prev_events = _event_dict_property("prev_events") 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") room_id = _event_dict_property("room_id")
sender = _event_dict_property("sender") sender = _event_dict_property("sender")
user_id = _event_dict_property("sender") user_id = _event_dict_property("sender")

View File

@ -951,7 +951,7 @@ class EventsStore(
elif event.type == EventTypes.Message: elif event.type == EventTypes.Message:
# Insert into the event_search table. # Insert into the event_search table.
self._store_room_message_txn(txn, event) 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. # Insert into the redactions table.
self._store_redaction(txn, event) self._store_redaction(txn, event)
elif event.type == EventTypes.Retention: elif event.type == EventTypes.Retention:

View File

@ -287,7 +287,7 @@ class EventsWorkerStore(SQLBaseStore):
# we have to recheck auth now. # we have to recheck auth now.
if not allow_rejected and entry.event.type == EventTypes.Redaction: 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 # A redacted redaction doesn't have a `redacts` key, in
# which case lets just withhold the event. # which case lets just withhold the event.
# #

View File

@ -398,3 +398,38 @@ class RedactionTestCase(unittest.HomeserverTestCase):
self.get_success( self.get_success(
self.store.get_event(first_redact_event.event_id, allow_none=True) 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)
)