Merge branch 'develop' of github.com:matrix-org/synapse into erikj/redactions_eiah

This commit is contained in:
Erik Johnston 2019-01-29 22:58:38 +00:00
commit 67b82f1336
9 changed files with 77 additions and 46 deletions

1
changelog.d/4514.misc Normal file
View File

@ -0,0 +1 @@
Add infrastructure to support different event formats

View File

@ -22,6 +22,7 @@ from unpaddedbase64 import decode_base64
from synapse.api.constants import ( from synapse.api.constants import (
KNOWN_ROOM_VERSIONS, KNOWN_ROOM_VERSIONS,
EventFormatVersions,
EventTypes, EventTypes,
JoinRules, JoinRules,
Membership, Membership,
@ -55,7 +56,6 @@ def check(room_version, event, auth_events, do_sig_check=True, do_size_check=Tru
if do_sig_check: if do_sig_check:
sender_domain = get_domain_from_id(event.sender) sender_domain = get_domain_from_id(event.sender)
event_id_domain = get_domain_from_id(event.event_id)
is_invite_via_3pid = ( is_invite_via_3pid = (
event.type == EventTypes.Member event.type == EventTypes.Member
@ -72,9 +72,13 @@ def check(room_version, event, auth_events, do_sig_check=True, do_size_check=Tru
if not is_invite_via_3pid: if not is_invite_via_3pid:
raise AuthError(403, "Event not signed by sender's server") raise AuthError(403, "Event not signed by sender's server")
# Check the event_id's domain has signed the event if event.format_version in (EventFormatVersions.V1,):
if not event.signatures.get(event_id_domain): # Only older room versions have event IDs to check.
raise AuthError(403, "Event not signed by sending server") event_id_domain = get_domain_from_id(event.event_id)
# Check the origin domain has signed the event
if not event.signatures.get(event_id_domain):
raise AuthError(403, "Event not signed by sending server")
if auth_events is None: if auth_events is None:
# Oh, we don't know what the state of the room was, so we # Oh, we don't know what the state of the room was, so we

View File

@ -15,7 +15,7 @@
from six import string_types from six import string_types
from synapse.api.constants import EventTypes, Membership from synapse.api.constants import EventFormatVersions, EventTypes, Membership
from synapse.api.errors import SynapseError from synapse.api.errors import SynapseError
from synapse.types import EventID, RoomID, UserID from synapse.types import EventID, RoomID, UserID
@ -29,7 +29,8 @@ class EventValidator(object):
""" """
self.validate_builder(event) self.validate_builder(event)
EventID.from_string(event.event_id) if event.format_version == EventFormatVersions.V1:
EventID.from_string(event.event_id)
required = [ required = [
"auth_events", "auth_events",

View File

@ -20,7 +20,7 @@ import six
from twisted.internet import defer from twisted.internet import defer
from twisted.internet.defer import DeferredList from twisted.internet.defer import DeferredList
from synapse.api.constants import MAX_DEPTH, EventTypes, Membership from synapse.api.constants import KNOWN_ROOM_VERSIONS, MAX_DEPTH, EventTypes, Membership
from synapse.api.errors import Codes, SynapseError from synapse.api.errors import Codes, SynapseError
from synapse.crypto.event_signing import check_event_content_hash from synapse.crypto.event_signing import check_event_content_hash
from synapse.events import event_type_from_format_version from synapse.events import event_type_from_format_version
@ -66,7 +66,7 @@ class FederationBase(object):
Returns: Returns:
Deferred : A list of PDUs that have valid signatures and hashes. Deferred : A list of PDUs that have valid signatures and hashes.
""" """
deferreds = self._check_sigs_and_hashes(pdus) deferreds = self._check_sigs_and_hashes(room_version, pdus)
@defer.inlineCallbacks @defer.inlineCallbacks
def handle_check_result(pdu, deferred): def handle_check_result(pdu, deferred):
@ -121,16 +121,17 @@ class FederationBase(object):
else: else:
defer.returnValue([p for p in valid_pdus if p]) defer.returnValue([p for p in valid_pdus if p])
def _check_sigs_and_hash(self, pdu): def _check_sigs_and_hash(self, room_version, pdu):
return logcontext.make_deferred_yieldable( return logcontext.make_deferred_yieldable(
self._check_sigs_and_hashes([pdu])[0], self._check_sigs_and_hashes(room_version, [pdu])[0],
) )
def _check_sigs_and_hashes(self, pdus): def _check_sigs_and_hashes(self, room_version, pdus):
"""Checks that each of the received events is correctly signed by the """Checks that each of the received events is correctly signed by the
sending server. sending server.
Args: Args:
room_version (str): The room version of the PDUs
pdus (list[FrozenEvent]): the events to be checked pdus (list[FrozenEvent]): the events to be checked
Returns: Returns:
@ -141,7 +142,7 @@ class FederationBase(object):
* throws a SynapseError if the signature check failed. * throws a SynapseError if the signature check failed.
The deferreds run their callbacks in the sentinel logcontext. The deferreds run their callbacks in the sentinel logcontext.
""" """
deferreds = _check_sigs_on_pdus(self.keyring, pdus) deferreds = _check_sigs_on_pdus(self.keyring, room_version, pdus)
ctx = logcontext.LoggingContext.current_context() ctx = logcontext.LoggingContext.current_context()
@ -203,16 +204,17 @@ class FederationBase(object):
class PduToCheckSig(namedtuple("PduToCheckSig", [ class PduToCheckSig(namedtuple("PduToCheckSig", [
"pdu", "redacted_pdu_json", "event_id_domain", "sender_domain", "deferreds", "pdu", "redacted_pdu_json", "sender_domain", "deferreds",
])): ])):
pass pass
def _check_sigs_on_pdus(keyring, pdus): def _check_sigs_on_pdus(keyring, room_version, pdus):
"""Check that the given events are correctly signed """Check that the given events are correctly signed
Args: Args:
keyring (synapse.crypto.Keyring): keyring object to do the checks keyring (synapse.crypto.Keyring): keyring object to do the checks
room_version (str): the room version of the PDUs
pdus (Collection[EventBase]): the events to be checked pdus (Collection[EventBase]): the events to be checked
Returns: Returns:
@ -225,9 +227,7 @@ def _check_sigs_on_pdus(keyring, pdus):
# we want to check that the event is signed by: # we want to check that the event is signed by:
# #
# (a) the server which created the event_id # (a) the sender's server
#
# (b) the sender's server.
# #
# - except in the case of invites created from a 3pid invite, which are exempt # - except in the case of invites created from a 3pid invite, which are exempt
# from this check, because the sender has to match that of the original 3pid # from this check, because the sender has to match that of the original 3pid
@ -241,34 +241,26 @@ def _check_sigs_on_pdus(keyring, pdus):
# and signatures are *supposed* to be valid whether or not an event has been # and signatures are *supposed* to be valid whether or not an event has been
# redacted. But this isn't the worst of the ways that 3pid invites are broken. # redacted. But this isn't the worst of the ways that 3pid invites are broken.
# #
# (b) for V1 and V2 rooms, the server which created the event_id
#
# let's start by getting the domain for each pdu, and flattening the event back # let's start by getting the domain for each pdu, and flattening the event back
# to JSON. # to JSON.
pdus_to_check = [ pdus_to_check = [
PduToCheckSig( PduToCheckSig(
pdu=p, pdu=p,
redacted_pdu_json=prune_event(p).get_pdu_json(), redacted_pdu_json=prune_event(p).get_pdu_json(),
event_id_domain=get_domain_from_id(p.event_id),
sender_domain=get_domain_from_id(p.sender), sender_domain=get_domain_from_id(p.sender),
deferreds=[], deferreds=[],
) )
for p in pdus for p in pdus
] ]
# first make sure that the event is signed by the event_id's domain # First we check that the sender event is signed by the sender's domain
deferreds = keyring.verify_json_objects_for_server([ # (except if its a 3pid invite, in which case it may be sent by any server)
(p.event_id_domain, p.redacted_pdu_json)
for p in pdus_to_check
])
for p, d in zip(pdus_to_check, deferreds):
p.deferreds.append(d)
# now let's look for events where the sender's domain is different to the
# event id's domain (normally only the case for joins/leaves), and add additional
# checks.
pdus_to_check_sender = [ pdus_to_check_sender = [
p for p in pdus_to_check p for p in pdus_to_check
if p.sender_domain != p.event_id_domain and not _is_invite_via_3pid(p.pdu) if not _is_invite_via_3pid(p.pdu)
] ]
more_deferreds = keyring.verify_json_objects_for_server([ more_deferreds = keyring.verify_json_objects_for_server([
@ -279,19 +271,37 @@ def _check_sigs_on_pdus(keyring, pdus):
for p, d in zip(pdus_to_check_sender, more_deferreds): for p, d in zip(pdus_to_check_sender, more_deferreds):
p.deferreds.append(d) p.deferreds.append(d)
# now let's look for events where the sender's domain is different to the
# event id's domain (normally only the case for joins/leaves), and add additional
# checks. Only do this if the room version has a concept of event ID domain
if room_version in KNOWN_ROOM_VERSIONS:
pdus_to_check_event_id = [
p for p in pdus_to_check
if p.sender_domain != get_domain_from_id(p.pdu.event_id)
]
more_deferreds = keyring.verify_json_objects_for_server([
(get_domain_from_id(p.pdu.event_id), p.redacted_pdu_json)
for p in pdus_to_check_event_id
])
for p, d in zip(pdus_to_check_event_id, more_deferreds):
p.deferreds.append(d)
# replace lists of deferreds with single Deferreds # replace lists of deferreds with single Deferreds
return [_flatten_deferred_list(p.deferreds) for p in pdus_to_check] return [_flatten_deferred_list(p.deferreds) for p in pdus_to_check]
def _flatten_deferred_list(deferreds): def _flatten_deferred_list(deferreds):
"""Given a list of one or more deferreds, either return the single deferred, or """Given a list of deferreds, either return the single deferred,
combine into a DeferredList. combine into a DeferredList, or return an already resolved deferred.
""" """
if len(deferreds) > 1: if len(deferreds) > 1:
return DeferredList(deferreds, fireOnOneErrback=True, consumeErrors=True) return DeferredList(deferreds, fireOnOneErrback=True, consumeErrors=True)
else: elif len(deferreds) == 1:
assert len(deferreds) == 1
return deferreds[0] return deferreds[0]
else:
return defer.succeed(None)
def _is_invite_via_3pid(event): def _is_invite_via_3pid(event):
@ -319,7 +329,7 @@ def event_from_pdu_json(pdu_json, event_format_version, outlier=False):
""" """
# we could probably enforce a bunch of other fields here (room_id, sender, # we could probably enforce a bunch of other fields here (room_id, sender,
# origin, etc etc) # origin, etc etc)
assert_params_in_dict(pdu_json, ('event_id', 'type', 'depth')) assert_params_in_dict(pdu_json, ('type', 'depth'))
depth = pdu_json['depth'] depth = pdu_json['depth']
if not isinstance(depth, six.integer_types): if not isinstance(depth, six.integer_types):

View File

@ -205,7 +205,7 @@ class FederationClient(FederationBase):
# FIXME: We should handle signature failures more gracefully. # FIXME: We should handle signature failures more gracefully.
pdus[:] = yield logcontext.make_deferred_yieldable(defer.gatherResults( pdus[:] = yield logcontext.make_deferred_yieldable(defer.gatherResults(
self._check_sigs_and_hashes(pdus), self._check_sigs_and_hashes(room_version, pdus),
consumeErrors=True, consumeErrors=True,
).addErrback(unwrapFirstError)) ).addErrback(unwrapFirstError))
@ -268,7 +268,7 @@ class FederationClient(FederationBase):
pdu = pdu_list[0] pdu = pdu_list[0]
# Check signatures are correct. # Check signatures are correct.
signed_pdu = yield self._check_sigs_and_hash(pdu) signed_pdu = yield self._check_sigs_and_hash(room_version, pdu)
break break
@ -757,7 +757,7 @@ class FederationClient(FederationBase):
pdu = event_from_pdu_json(pdu_dict, format_ver) pdu = event_from_pdu_json(pdu_dict, format_ver)
# Check signatures are correct. # Check signatures are correct.
pdu = yield self._check_sigs_and_hash(pdu) pdu = yield self._check_sigs_and_hash(room_version, pdu)
# FIXME: We should handle signature failures more gracefully. # FIXME: We should handle signature failures more gracefully.

View File

@ -25,7 +25,7 @@ from twisted.internet import defer
from twisted.internet.abstract import isIPAddress from twisted.internet.abstract import isIPAddress
from twisted.python import failure from twisted.python import failure
from synapse.api.constants import EventTypes from synapse.api.constants import EventTypes, Membership
from synapse.api.errors import ( from synapse.api.errors import (
AuthError, AuthError,
FederationError, FederationError,
@ -620,16 +620,19 @@ class FederationServer(FederationBase):
""" """
# check that it's actually being sent from a valid destination to # check that it's actually being sent from a valid destination to
# workaround bug #1753 in 0.18.5 and 0.18.6 # workaround bug #1753 in 0.18.5 and 0.18.6
if origin != get_domain_from_id(pdu.event_id): if origin != get_domain_from_id(pdu.sender):
# We continue to accept join events from any server; this is # We continue to accept join events from any server; this is
# necessary for the federation join dance to work correctly. # necessary for the federation join dance to work correctly.
# (When we join over federation, the "helper" server is # (When we join over federation, the "helper" server is
# responsible for sending out the join event, rather than the # responsible for sending out the join event, rather than the
# origin. See bug #1893). # origin. See bug #1893. This is also true for some third party
# invites).
if not ( if not (
pdu.type == 'm.room.member' and pdu.type == 'm.room.member' and
pdu.content and pdu.content and
pdu.content.get("membership", None) == 'join' pdu.content.get("membership", None) in (
Membership.JOIN, Membership.INVITE,
)
): ):
logger.info( logger.info(
"Discarding PDU %s from invalid origin %s", "Discarding PDU %s from invalid origin %s",
@ -642,9 +645,12 @@ class FederationServer(FederationBase):
pdu.event_id, origin pdu.event_id, origin
) )
# We've already checked that we know the room version by this point
room_version = yield self.store.get_room_version(pdu.room_id)
# Check signature. # Check signature.
try: try:
pdu = yield self._check_sigs_and_hash(pdu) pdu = yield self._check_sigs_and_hash(room_version, pdu)
except SynapseError as e: except SynapseError as e:
raise FederationError( raise FederationError(
"ERROR", "ERROR",

View File

@ -175,7 +175,7 @@ class TransactionQueue(object):
def handle_event(event): def handle_event(event):
# Only send events for this server. # Only send events for this server.
send_on_behalf_of = event.internal_metadata.get_send_on_behalf_of() send_on_behalf_of = event.internal_metadata.get_send_on_behalf_of()
is_mine = self.is_mine_id(event.event_id) is_mine = self.is_mine_id(event.sender)
if not is_mine and send_on_behalf_of is None: if not is_mine and send_on_behalf_of is None:
return return

View File

@ -2293,6 +2293,10 @@ class FederationHandler(BaseHandler):
EventValidator().validate_new(event) EventValidator().validate_new(event)
# We need to tell the transaction queue to send this out, even
# though the sender isn't a local user.
event.internal_metadata.send_on_behalf_of = self.hs.hostname
try: try:
yield self.auth.check_from_context(room_version, event, context) yield self.auth.check_from_context(room_version, event, context)
except AuthError as e: except AuthError as e:
@ -2342,6 +2346,10 @@ class FederationHandler(BaseHandler):
raise e raise e
yield self._check_signature(event, context) yield self._check_signature(event, context)
# We need to tell the transaction queue to send this out, even
# though the sender isn't a local user.
event.internal_metadata.send_on_behalf_of = get_domain_from_id(event.sender)
# XXX we send the invite here, but send_membership_event also sends it, # XXX we send the invite here, but send_membership_event also sends it,
# so we end up making two requests. I think this is redundant. # so we end up making two requests. I think this is redundant.
returned_invite = yield self.send_invite(origin, event) returned_invite = yield self.send_invite(origin, event)

View File

@ -940,7 +940,8 @@ class RoomMemberHandler(object):
# first member event? # first member event?
create_event_id = current_state_ids.get(("m.room.create", "")) create_event_id = current_state_ids.get(("m.room.create", ""))
if len(current_state_ids) == 1 and create_event_id: if len(current_state_ids) == 1 and create_event_id:
defer.returnValue(self.hs.is_mine_id(create_event_id)) # We can only get here if we're in the process of creating the room
defer.returnValue(True)
for etype, state_key in current_state_ids: for etype, state_key in current_state_ids:
if etype != EventTypes.Member or not self.hs.is_mine_id(state_key): if etype != EventTypes.Member or not self.hs.is_mine_id(state_key):