Implement soft fail

This commit is contained in:
Erik Johnston 2019-02-12 10:31:21 +00:00
parent 16c8b4ecbd
commit a9de04be72
4 changed files with 95 additions and 1 deletions

View file

@ -45,6 +45,7 @@ from synapse.api.errors import (
SynapseError,
)
from synapse.crypto.event_signing import compute_event_signature
from synapse.event_auth import auth_types_for_event
from synapse.events.validator import EventValidator
from synapse.replication.http.federation import (
ReplicationCleanRoomRestServlet,
@ -1628,6 +1629,7 @@ class FederationHandler(BaseHandler):
origin, event,
state=state,
auth_events=auth_events,
backfilled=backfilled,
)
# reraise does not allow inlineCallbacks to preserve the stacktrace, so we
@ -1672,6 +1674,7 @@ class FederationHandler(BaseHandler):
event,
state=ev_info.get("state"),
auth_events=ev_info.get("auth_events"),
backfilled=backfilled,
)
defer.returnValue(res)
@ -1794,7 +1797,7 @@ class FederationHandler(BaseHandler):
)
@defer.inlineCallbacks
def _prep_event(self, origin, event, state=None, auth_events=None):
def _prep_event(self, origin, event, state, auth_events, backfilled):
"""
Args:
@ -1802,6 +1805,7 @@ class FederationHandler(BaseHandler):
event:
state:
auth_events:
backfilled (bool)
Returns:
Deferred, which resolves to synapse.events.snapshot.EventContext
@ -1843,6 +1847,77 @@ class FederationHandler(BaseHandler):
context.rejected = RejectedReason.AUTH_ERROR
# For new (non-backfilled and non-outlier) events we check if the event
# passes auth based on the current state. If it doesn't then we
# "soft-fail" the event.
do_soft_fail_check = not backfilled and not event.internal_metadata.is_outlier()
if do_soft_fail_check:
extrem_ids = yield self.store.get_latest_event_ids_in_room(
event.room_id,
)
extrem_ids = set(extrem_ids)
prev_event_ids = set(event.prev_event_ids())
if extrem_ids == prev_event_ids:
# If they're the same then the current state is the same as the
# state at the event, so no point rechecking auth for soft fail.
do_soft_fail_check = False
if do_soft_fail_check:
room_version = yield self.store.get_room_version(event.room_id)
# Calculate the "current state".
if state is not None:
# If we're explicitly given the state then we won't have all the
# prev events, and so we have a gap in the graph. In this case
# we want to be a little careful as we might have been down for
# a while and have an incorrect view of the current state,
# however we still want to do checks as gaps are easy to
# maliciously manufacture.
#
# So we use a "current state" that is actually a state
# resolution across the current forward extremities and the
# given state at the event. This should correctly handle cases
# like bans, especially with state res v2.
state_sets = yield self.store.get_state_groups(
event.room_id, extrem_ids,
)
state_sets = list(state_sets.values())
state_sets.append(state)
current_state_ids = yield self.state_handler.resolve_events(
room_version, state_sets, event,
)
current_state_ids = {
k: e.event_id for k, e in iteritems(current_state_ids)
}
else:
current_state_ids = yield self.state_handler.get_current_state_ids(
event.room_id, latest_event_ids=extrem_ids,
)
# Now check if event pass auth against said current state
auth_types = auth_types_for_event(event)
current_state_ids = [
e for k, e in iteritems(current_state_ids)
if k in auth_types
]
current_auth_events = yield self.store.get_events(current_state_ids)
current_auth_events = {
(e.type, e.state_key): e for e in current_auth_events.values()
}
try:
self.auth.check(room_version, event, auth_events=current_auth_events)
except AuthError as e:
logger.warn(
"Failed current state auth resolution for %r because %s",
event, e,
)
event.internal_metadata.soft_failed = True
if event.type == EventTypes.GuestAccess and not context.rejected:
yield self.maybe_kick_guest_users(event)