mirror of
https://mau.dev/maunium/synapse.git
synced 2024-12-15 00:43:51 +01:00
Merge pull request #4499 from matrix-org/erikj/redactions_eiah
Implement rechecking of redactions for room versions v3
This commit is contained in:
commit
e12313ba25
8 changed files with 77 additions and 16 deletions
1
changelog.d/4499.feature
Normal file
1
changelog.d/4499.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add support for room version 3
|
|
@ -616,7 +616,7 @@ class Auth(object):
|
||||||
|
|
||||||
defer.returnValue(auth_ids)
|
defer.returnValue(auth_ids)
|
||||||
|
|
||||||
def check_redaction(self, event, auth_events):
|
def check_redaction(self, room_version, event, auth_events):
|
||||||
"""Check whether the event sender is allowed to redact the target event.
|
"""Check whether the event sender is allowed to redact the target event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -629,7 +629,7 @@ class Auth(object):
|
||||||
AuthError if the event sender is definitely not allowed to redact
|
AuthError if the event sender is definitely not allowed to redact
|
||||||
the target event.
|
the target event.
|
||||||
"""
|
"""
|
||||||
return event_auth.check_redaction(event, auth_events)
|
return event_auth.check_redaction(room_version, event, auth_events)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_can_change_room_list(self, room_id, user):
|
def check_can_change_room_list(self, room_id, user):
|
||||||
|
|
|
@ -104,7 +104,7 @@ class ThirdPartyEntityKind(object):
|
||||||
class RoomVersions(object):
|
class RoomVersions(object):
|
||||||
V1 = "1"
|
V1 = "1"
|
||||||
V2 = "2"
|
V2 = "2"
|
||||||
VDH_TEST = "vdh-test-version"
|
V3 = "3" # Not currently fully supported, so we don't add to known versions below
|
||||||
STATE_V2_TEST = "state-v2-test"
|
STATE_V2_TEST = "state-v2-test"
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,7 +116,6 @@ DEFAULT_ROOM_VERSION = RoomVersions.V1
|
||||||
KNOWN_ROOM_VERSIONS = {
|
KNOWN_ROOM_VERSIONS = {
|
||||||
RoomVersions.V1,
|
RoomVersions.V1,
|
||||||
RoomVersions.V2,
|
RoomVersions.V2,
|
||||||
RoomVersions.VDH_TEST,
|
|
||||||
RoomVersions.STATE_V2_TEST,
|
RoomVersions.STATE_V2_TEST,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from synapse.api.constants import (
|
||||||
EventTypes,
|
EventTypes,
|
||||||
JoinRules,
|
JoinRules,
|
||||||
Membership,
|
Membership,
|
||||||
|
RoomVersions,
|
||||||
)
|
)
|
||||||
from synapse.api.errors import AuthError, EventSizeError, SynapseError
|
from synapse.api.errors import AuthError, EventSizeError, SynapseError
|
||||||
from synapse.types import UserID, get_domain_from_id
|
from synapse.types import UserID, get_domain_from_id
|
||||||
|
@ -177,7 +178,7 @@ def check(room_version, event, auth_events, do_sig_check=True, do_size_check=Tru
|
||||||
_check_power_levels(event, auth_events)
|
_check_power_levels(event, auth_events)
|
||||||
|
|
||||||
if event.type == EventTypes.Redaction:
|
if event.type == EventTypes.Redaction:
|
||||||
check_redaction(event, auth_events)
|
check_redaction(room_version, event, auth_events)
|
||||||
|
|
||||||
logger.debug("Allowing! %s", event)
|
logger.debug("Allowing! %s", event)
|
||||||
|
|
||||||
|
@ -431,7 +432,7 @@ def _can_send_event(event, auth_events):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_redaction(event, auth_events):
|
def check_redaction(room_version, event, auth_events):
|
||||||
"""Check whether the event sender is allowed to redact the target event.
|
"""Check whether the event sender is allowed to redact the target event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -451,10 +452,16 @@ def check_redaction(event, auth_events):
|
||||||
if user_level >= redact_level:
|
if user_level >= redact_level:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
redacter_domain = get_domain_from_id(event.event_id)
|
if room_version in (RoomVersions.V1, RoomVersions.V2,):
|
||||||
redactee_domain = get_domain_from_id(event.redacts)
|
redacter_domain = get_domain_from_id(event.event_id)
|
||||||
if redacter_domain == redactee_domain:
|
redactee_domain = get_domain_from_id(event.redacts)
|
||||||
|
if redacter_domain == redactee_domain:
|
||||||
|
return True
|
||||||
|
elif room_version == RoomVersions.V3:
|
||||||
|
event.internal_metadata.recheck_redaction = True
|
||||||
return True
|
return True
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Unrecognized room version %r" % (room_version,))
|
||||||
|
|
||||||
raise AuthError(
|
raise AuthError(
|
||||||
403,
|
403,
|
||||||
|
|
|
@ -62,6 +62,21 @@ class _EventInternalMetadata(object):
|
||||||
"""
|
"""
|
||||||
return getattr(self, "send_on_behalf_of", None)
|
return getattr(self, "send_on_behalf_of", None)
|
||||||
|
|
||||||
|
def need_to_check_redaction(self):
|
||||||
|
"""Whether the redaction event needs to be rechecked when fetching
|
||||||
|
from the database.
|
||||||
|
|
||||||
|
Starting in room v3 redaction events are accepted up front, and later
|
||||||
|
checked to see if the redacter and redactee's domains match.
|
||||||
|
|
||||||
|
If the sender of the redaction event is allowed to redact any event
|
||||||
|
due to auth rules, then this will always return false.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool
|
||||||
|
"""
|
||||||
|
return getattr(self, "recheck_redaction", False)
|
||||||
|
|
||||||
|
|
||||||
def _event_dict_property(key):
|
def _event_dict_property(key):
|
||||||
# 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.
|
||||||
|
@ -328,8 +343,7 @@ def room_version_to_event_format(room_version):
|
||||||
raise RuntimeError("Unrecognized room version %s" % (room_version,))
|
raise RuntimeError("Unrecognized room version %s" % (room_version,))
|
||||||
|
|
||||||
if room_version in (
|
if room_version in (
|
||||||
RoomVersions.V1, RoomVersions.V2, RoomVersions.VDH_TEST,
|
RoomVersions.V1, RoomVersions.V2, RoomVersions.STATE_V2_TEST,
|
||||||
RoomVersions.STATE_V2_TEST,
|
|
||||||
):
|
):
|
||||||
return EventFormatVersions.V1
|
return EventFormatVersions.V1
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -745,7 +745,8 @@ class EventCreationHandler(object):
|
||||||
auth_events = {
|
auth_events = {
|
||||||
(e.type, e.state_key): e for e in auth_events.values()
|
(e.type, e.state_key): e for e in auth_events.values()
|
||||||
}
|
}
|
||||||
if self.auth.check_redaction(event, auth_events=auth_events):
|
room_version = yield self.store.get_room_version(event.room_id)
|
||||||
|
if self.auth.check_redaction(room_version, event, auth_events=auth_events):
|
||||||
original_event = yield self.store.get_event(
|
original_event = yield self.store.get_event(
|
||||||
event.redacts,
|
event.redacts,
|
||||||
check_redacted=False,
|
check_redacted=False,
|
||||||
|
@ -759,6 +760,9 @@ class EventCreationHandler(object):
|
||||||
"You don't have permission to redact events"
|
"You don't have permission to redact events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# We've already checked.
|
||||||
|
event.internal_metadata.recheck_redaction = False
|
||||||
|
|
||||||
if event.type == EventTypes.Create:
|
if event.type == EventTypes.Create:
|
||||||
prev_state_ids = yield context.get_prev_state_ids(self.store)
|
prev_state_ids = yield context.get_prev_state_ids(self.store)
|
||||||
if prev_state_ids:
|
if prev_state_ids:
|
||||||
|
|
|
@ -608,7 +608,7 @@ def resolve_events_with_store(room_version, state_sets, event_map, state_res_sto
|
||||||
state_sets, event_map, state_res_store.get_events,
|
state_sets, event_map, state_res_store.get_events,
|
||||||
)
|
)
|
||||||
elif room_version in (
|
elif room_version in (
|
||||||
RoomVersions.VDH_TEST, RoomVersions.STATE_V2_TEST, RoomVersions.V2,
|
RoomVersions.STATE_V2_TEST, RoomVersions.V2,
|
||||||
):
|
):
|
||||||
return v2.resolve_events_with_store(
|
return v2.resolve_events_with_store(
|
||||||
room_version, state_sets, event_map, state_res_store,
|
room_version, state_sets, event_map, state_res_store,
|
||||||
|
|
|
@ -21,13 +21,14 @@ from canonicaljson import json
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.constants import EventFormatVersions
|
from synapse.api.constants import EventFormatVersions, EventTypes
|
||||||
from synapse.api.errors import NotFoundError
|
from synapse.api.errors import NotFoundError
|
||||||
from synapse.events import FrozenEvent, event_type_from_format_version # noqa: F401
|
from synapse.events import FrozenEvent, event_type_from_format_version # noqa: F401
|
||||||
# these are only included to make the type annotations work
|
# these are only included to make the type annotations work
|
||||||
from synapse.events.snapshot import EventContext # noqa: F401
|
from synapse.events.snapshot import EventContext # noqa: F401
|
||||||
from synapse.events.utils import prune_event
|
from synapse.events.utils import prune_event
|
||||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
|
from synapse.types import get_domain_from_id
|
||||||
from synapse.util.logcontext import (
|
from synapse.util.logcontext import (
|
||||||
LoggingContext,
|
LoggingContext,
|
||||||
PreserveLoggingContext,
|
PreserveLoggingContext,
|
||||||
|
@ -162,7 +163,6 @@ class EventsWorkerStore(SQLBaseStore):
|
||||||
|
|
||||||
missing_events = yield self._enqueue_events(
|
missing_events = yield self._enqueue_events(
|
||||||
missing_events_ids,
|
missing_events_ids,
|
||||||
check_redacted=check_redacted,
|
|
||||||
allow_rejected=allow_rejected,
|
allow_rejected=allow_rejected,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -174,6 +174,29 @@ class EventsWorkerStore(SQLBaseStore):
|
||||||
if not entry:
|
if not entry:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Starting in room version v3, some redactions need to be rechecked if we
|
||||||
|
# didn't have the redacted event at the time, so we recheck on read
|
||||||
|
# instead.
|
||||||
|
if not allow_rejected and entry.event.type == EventTypes.Redaction:
|
||||||
|
if entry.event.internal_metadata.need_to_check_redaction():
|
||||||
|
orig = yield self.get_event(
|
||||||
|
entry.event.redacts,
|
||||||
|
allow_none=True,
|
||||||
|
allow_rejected=True,
|
||||||
|
get_prev_content=False,
|
||||||
|
)
|
||||||
|
expected_domain = get_domain_from_id(entry.event.sender)
|
||||||
|
if orig and get_domain_from_id(orig.sender) == expected_domain:
|
||||||
|
# This redaction event is allowed. Mark as not needing a
|
||||||
|
# recheck.
|
||||||
|
entry.event.internal_metadata.recheck_redaction = False
|
||||||
|
else:
|
||||||
|
# We don't have the event that is being redacted, so we
|
||||||
|
# assume that the event isn't authorized for now. (If we
|
||||||
|
# later receive the event, then we will always redact
|
||||||
|
# it anyway, since we have this redaction)
|
||||||
|
continue
|
||||||
|
|
||||||
if allow_rejected or not entry.event.rejected_reason:
|
if allow_rejected or not entry.event.rejected_reason:
|
||||||
if check_redacted and entry.redacted_event:
|
if check_redacted and entry.redacted_event:
|
||||||
event = entry.redacted_event
|
event = entry.redacted_event
|
||||||
|
@ -310,7 +333,7 @@ class EventsWorkerStore(SQLBaseStore):
|
||||||
self.hs.get_reactor().callFromThread(fire, event_list, e)
|
self.hs.get_reactor().callFromThread(fire, event_list, e)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _enqueue_events(self, events, check_redacted=True, allow_rejected=False):
|
def _enqueue_events(self, events, allow_rejected=False):
|
||||||
"""Fetches events from the database using the _event_fetch_list. This
|
"""Fetches events from the database using the _event_fetch_list. This
|
||||||
allows batch and bulk fetching of events - it allows us to fetch events
|
allows batch and bulk fetching of events - it allows us to fetch events
|
||||||
without having to create a new transaction for each request for events.
|
without having to create a new transaction for each request for events.
|
||||||
|
@ -443,6 +466,19 @@ class EventsWorkerStore(SQLBaseStore):
|
||||||
# will serialise this field correctly
|
# will serialise this field correctly
|
||||||
redacted_event.unsigned["redacted_because"] = because
|
redacted_event.unsigned["redacted_because"] = because
|
||||||
|
|
||||||
|
# Starting in room version v3, some redactions need to be
|
||||||
|
# rechecked if we didn't have the redacted event at the
|
||||||
|
# time, so we recheck on read instead.
|
||||||
|
if because.internal_metadata.need_to_check_redaction():
|
||||||
|
expected_domain = get_domain_from_id(original_ev.sender)
|
||||||
|
if get_domain_from_id(because.sender) == expected_domain:
|
||||||
|
# This redaction event is allowed. Mark as not needing a
|
||||||
|
# recheck.
|
||||||
|
because.internal_metadata.recheck_redaction = False
|
||||||
|
else:
|
||||||
|
# Senders don't match, so the event isn't actually redacted
|
||||||
|
redacted_event = None
|
||||||
|
|
||||||
cache_entry = _EventCacheEntry(
|
cache_entry = _EventCacheEntry(
|
||||||
event=original_ev,
|
event=original_ev,
|
||||||
redacted_event=redacted_event,
|
redacted_event=redacted_event,
|
||||||
|
|
Loading…
Reference in a new issue