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

matrix-org-hotfixes-identity
Erik Johnston 2019-08-01 14:46:09 +01:00
commit 873ff9522b
27 changed files with 152 additions and 89 deletions

View File

@ -1,5 +1,4 @@
comment: comment: off
layout: "diff"
coverage: coverage:
status: status:

1
changelog.d/5686.feature Normal file
View File

@ -0,0 +1 @@
Use `M_USER_DEACTIVATED` instead of `M_UNKNOWN` for errcode when a deactivated user attempts to login.

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

@ -0,0 +1 @@
Remove DelayedCall debugging from the test suite, as it is no longer required in the vast majority of Synapse's tests.

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

@ -0,0 +1 @@
Remove some spurious exceptions from the logs where we failed to talk to a remote server.

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

@ -0,0 +1 @@
Improve performance when making `.well-known` requests by sharing the SSL options between requests.

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

@ -0,0 +1 @@
Disable codecov GitHub comments on PRs.

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

@ -0,0 +1 @@
Don't allow clients to send tombstone events that reference the room it's sent in.

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

@ -0,0 +1 @@
Deny redactions of events sent in a different room.

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

@ -0,0 +1 @@
Fix check that tombstone is a state event in push rules.

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

@ -0,0 +1 @@
Deny sending well known state types as non-state events.

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

@ -0,0 +1 @@
Fix error when trying to login as a deactivated user when using a worker to handle login.

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

@ -0,0 +1 @@
Handle incorrectly encoded query params correctly by returning a 400.

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

@ -0,0 +1 @@
Return 502 not 500 when failing to reach any remote server.

View File

@ -61,6 +61,7 @@ class Codes(object):
INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION" INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION"
WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION" WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
EXPIRED_ACCOUNT = "ORG_MATRIX_EXPIRED_ACCOUNT" EXPIRED_ACCOUNT = "ORG_MATRIX_EXPIRED_ACCOUNT"
USER_DEACTIVATED = "M_USER_DEACTIVATED"
class CodeMessageException(RuntimeError): class CodeMessageException(RuntimeError):
@ -151,7 +152,7 @@ class UserDeactivatedError(SynapseError):
msg (str): The human-readable error message msg (str): The human-readable error message
""" """
super(UserDeactivatedError, self).__init__( super(UserDeactivatedError, self).__init__(
code=http_client.FORBIDDEN, msg=msg, errcode=Codes.UNKNOWN code=http_client.FORBIDDEN, msg=msg, errcode=Codes.USER_DEACTIVATED
) )

View File

@ -31,6 +31,7 @@ from twisted.internet.ssl import (
platformTrust, platformTrust,
) )
from twisted.python.failure import Failure from twisted.python.failure import Failure
from twisted.web.iweb import IPolicyForHTTPS
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -74,6 +75,7 @@ class ServerContextFactory(ContextFactory):
return self._context return self._context
@implementer(IPolicyForHTTPS)
class ClientTLSOptionsFactory(object): class ClientTLSOptionsFactory(object):
"""Factory for Twisted SSLClientConnectionCreators that are used to make connections """Factory for Twisted SSLClientConnectionCreators that are used to make connections
to remote servers for federation. to remote servers for federation.
@ -146,6 +148,12 @@ class ClientTLSOptionsFactory(object):
f = Failure() f = Failure()
tls_protocol.failVerification(f) tls_protocol.failVerification(f)
def creatorForNetloc(self, hostname, port):
"""Implements the IPolicyForHTTPS interace so that this can be passed
directly to agents.
"""
return self.get_options(hostname)
@implementer(IOpenSSLClientConnectionCreator) @implementer(IOpenSSLClientConnectionCreator)
class SSLClientConnectionCreator(object): class SSLClientConnectionCreator(object):

View File

@ -95,10 +95,10 @@ class EventValidator(object):
elif event.type == EventTypes.Topic: elif event.type == EventTypes.Topic:
self._ensure_strings(event.content, ["topic"]) self._ensure_strings(event.content, ["topic"])
self._ensure_state_event(event)
elif event.type == EventTypes.Name: elif event.type == EventTypes.Name:
self._ensure_strings(event.content, ["name"]) self._ensure_strings(event.content, ["name"])
self._ensure_state_event(event)
elif event.type == EventTypes.Member: elif event.type == EventTypes.Member:
if "membership" not in event.content: if "membership" not in event.content:
raise SynapseError(400, "Content has not membership key") raise SynapseError(400, "Content has not membership key")
@ -106,9 +106,25 @@ class EventValidator(object):
if event.content["membership"] not in Membership.LIST: if event.content["membership"] not in Membership.LIST:
raise SynapseError(400, "Invalid membership key") raise SynapseError(400, "Invalid membership key")
self._ensure_state_event(event)
elif event.type == EventTypes.Tombstone:
if "replacement_room" not in event.content:
raise SynapseError(400, "Content has no replacement_room key")
if event.content["replacement_room"] == event.room_id:
raise SynapseError(
400, "Tombstone cannot reference the room it was sent in"
)
self._ensure_state_event(event)
def _ensure_strings(self, d, keys): def _ensure_strings(self, d, keys):
for s in keys: for s in keys:
if s not in d: if s not in d:
raise SynapseError(400, "'%s' not in content" % (s,)) raise SynapseError(400, "'%s' not in content" % (s,))
if not isinstance(d[s], string_types): if not isinstance(d[s], string_types):
raise SynapseError(400, "'%s' not a string type" % (s,)) raise SynapseError(400, "'%s' not a string type" % (s,))
def _ensure_state_event(self, event):
if not event.is_state():
raise SynapseError(400, "'%s' must be state events" % (event.type,))

View File

@ -511,9 +511,8 @@ class FederationClient(FederationBase):
The [Deferred] result of callback, if it succeeds The [Deferred] result of callback, if it succeeds
Raises: Raises:
SynapseError if the chosen remote server returns a 300/400 code. SynapseError if the chosen remote server returns a 300/400 code, or
no servers were reachable.
RuntimeError if no servers were reachable.
""" """
for destination in destinations: for destination in destinations:
if destination == self.server_name: if destination == self.server_name:
@ -538,7 +537,7 @@ class FederationClient(FederationBase):
except Exception: except Exception:
logger.warn("Failed to %s via %s", description, destination, exc_info=1) logger.warn("Failed to %s via %s", description, destination, exc_info=1)
raise RuntimeError("Failed to %s via any server" % (description,)) raise SynapseError(502, "Failed to %s via any server" % (description,))
def make_membership_event( def make_membership_event(
self, destinations, room_id, user_id, membership, content, params self, destinations, room_id, user_id, membership, content, params

View File

@ -278,7 +278,6 @@ class DirectoryHandler(BaseHandler):
servers = list(servers) servers = list(servers)
return {"room_id": room_id, "servers": servers} return {"room_id": room_id, "servers": servers}
return
@defer.inlineCallbacks @defer.inlineCallbacks
def on_directory_query(self, args): def on_directory_query(self, args):

View File

@ -978,6 +978,9 @@ class FederationHandler(BaseHandler):
except NotRetryingDestination as e: except NotRetryingDestination as e:
logger.info(str(e)) logger.info(str(e))
continue continue
except RequestSendFailed as e:
logger.info("Falied to get backfill from %s because %s", dom, e)
continue
except FederationDeniedError as e: except FederationDeniedError as e:
logger.info(e) logger.info(e)
continue continue

View File

@ -126,9 +126,12 @@ class GroupsLocalHandler(object):
group_id, requester_user_id group_id, requester_user_id
) )
else: else:
try:
res = yield self.transport_client.get_group_summary( res = yield self.transport_client.get_group_summary(
get_domain_from_id(group_id), group_id, requester_user_id get_domain_from_id(group_id), group_id, requester_user_id
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
group_server_name = get_domain_from_id(group_id) group_server_name = get_domain_from_id(group_id)
@ -183,9 +186,12 @@ class GroupsLocalHandler(object):
content["user_profile"] = yield self.profile_handler.get_profile(user_id) content["user_profile"] = yield self.profile_handler.get_profile(user_id)
try:
res = yield self.transport_client.create_group( res = yield self.transport_client.create_group(
get_domain_from_id(group_id), group_id, user_id, content get_domain_from_id(group_id), group_id, user_id, content
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
remote_attestation = res["attestation"] remote_attestation = res["attestation"]
yield self.attestations.verify_attestation( yield self.attestations.verify_attestation(
@ -221,9 +227,12 @@ class GroupsLocalHandler(object):
group_server_name = get_domain_from_id(group_id) group_server_name = get_domain_from_id(group_id)
try:
res = yield self.transport_client.get_users_in_group( res = yield self.transport_client.get_users_in_group(
get_domain_from_id(group_id), group_id, requester_user_id get_domain_from_id(group_id), group_id, requester_user_id
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
chunk = res["chunk"] chunk = res["chunk"]
valid_entries = [] valid_entries = []
@ -258,9 +267,12 @@ class GroupsLocalHandler(object):
local_attestation = self.attestations.create_attestation(group_id, user_id) local_attestation = self.attestations.create_attestation(group_id, user_id)
content["attestation"] = local_attestation content["attestation"] = local_attestation
try:
res = yield self.transport_client.join_group( res = yield self.transport_client.join_group(
get_domain_from_id(group_id), group_id, user_id, content get_domain_from_id(group_id), group_id, user_id, content
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
remote_attestation = res["attestation"] remote_attestation = res["attestation"]
@ -299,9 +311,12 @@ class GroupsLocalHandler(object):
local_attestation = self.attestations.create_attestation(group_id, user_id) local_attestation = self.attestations.create_attestation(group_id, user_id)
content["attestation"] = local_attestation content["attestation"] = local_attestation
try:
res = yield self.transport_client.accept_group_invite( res = yield self.transport_client.accept_group_invite(
get_domain_from_id(group_id), group_id, user_id, content get_domain_from_id(group_id), group_id, user_id, content
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
remote_attestation = res["attestation"] remote_attestation = res["attestation"]
@ -338,6 +353,7 @@ class GroupsLocalHandler(object):
group_id, user_id, requester_user_id, content group_id, user_id, requester_user_id, content
) )
else: else:
try:
res = yield self.transport_client.invite_to_group( res = yield self.transport_client.invite_to_group(
get_domain_from_id(group_id), get_domain_from_id(group_id),
group_id, group_id,
@ -345,6 +361,8 @@ class GroupsLocalHandler(object):
requester_user_id, requester_user_id,
content, content,
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
return res return res
@ -398,6 +416,7 @@ class GroupsLocalHandler(object):
) )
else: else:
content["requester_user_id"] = requester_user_id content["requester_user_id"] = requester_user_id
try:
res = yield self.transport_client.remove_user_from_group( res = yield self.transport_client.remove_user_from_group(
get_domain_from_id(group_id), get_domain_from_id(group_id),
group_id, group_id,
@ -405,6 +424,8 @@ class GroupsLocalHandler(object):
user_id, user_id,
content, content,
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
return res return res
@ -435,9 +456,13 @@ class GroupsLocalHandler(object):
return {"groups": result} return {"groups": result}
else: else:
try:
bulk_result = yield self.transport_client.bulk_get_publicised_groups( bulk_result = yield self.transport_client.bulk_get_publicised_groups(
get_domain_from_id(user_id), [user_id] get_domain_from_id(user_id), [user_id]
) )
except RequestSendFailed:
raise SynapseError(502, "Failed to contact group server")
result = bulk_result.get("users", {}).get(user_id) result = bulk_result.get("users", {}).get(user_id)
# TODO: Verify attestations # TODO: Verify attestations
return {"groups": result} return {"groups": result}

View File

@ -795,7 +795,6 @@ class EventCreationHandler(object):
get_prev_content=False, get_prev_content=False,
allow_rejected=False, allow_rejected=False,
allow_none=True, allow_none=True,
check_room_id=event.room_id,
) )
# we can make some additional checks now if we have the original event. # we can make some additional checks now if we have the original event.
@ -803,6 +802,9 @@ class EventCreationHandler(object):
if original_event.type == EventTypes.Create: if original_event.type == EventTypes.Create:
raise AuthError(403, "Redacting create events is not permitted") raise AuthError(403, "Redacting create events is not permitted")
if original_event.room_id != event.room_id:
raise SynapseError(400, "Cannot redact event from a different room")
prev_state_ids = yield context.get_prev_state_ids(self.store) prev_state_ids = yield context.get_prev_state_ids(self.store)
auth_events_ids = yield self.auth.compute_auth_events( auth_events_ids = yield self.auth.compute_auth_events(
event, prev_state_ids, for_verification=True event, prev_state_ids, for_verification=True

View File

@ -64,10 +64,6 @@ class MatrixFederationAgent(object):
tls_client_options_factory (ClientTLSOptionsFactory|None): tls_client_options_factory (ClientTLSOptionsFactory|None):
factory to use for fetching client tls options, or none to disable TLS. factory to use for fetching client tls options, or none to disable TLS.
_well_known_tls_policy (IPolicyForHTTPS|None):
TLS policy to use for fetching .well-known files. None to use a default
(browser-like) implementation.
_srv_resolver (SrvResolver|None): _srv_resolver (SrvResolver|None):
SRVResolver impl to use for looking up SRV records. None to use a default SRVResolver impl to use for looking up SRV records. None to use a default
implementation. implementation.
@ -81,7 +77,6 @@ class MatrixFederationAgent(object):
self, self,
reactor, reactor,
tls_client_options_factory, tls_client_options_factory,
_well_known_tls_policy=None,
_srv_resolver=None, _srv_resolver=None,
_well_known_cache=well_known_cache, _well_known_cache=well_known_cache,
): ):
@ -98,13 +93,12 @@ class MatrixFederationAgent(object):
self._pool.maxPersistentPerHost = 5 self._pool.maxPersistentPerHost = 5
self._pool.cachedConnectionTimeout = 2 * 60 self._pool.cachedConnectionTimeout = 2 * 60
agent_args = {}
if _well_known_tls_policy is not None:
# the param is called 'contextFactory', but actually passing a
# contextfactory is deprecated, and it expects an IPolicyForHTTPS.
agent_args["contextFactory"] = _well_known_tls_policy
_well_known_agent = RedirectAgent( _well_known_agent = RedirectAgent(
Agent(self._reactor, pool=self._pool, **agent_args) Agent(
self._reactor,
pool=self._pool,
contextFactory=tls_client_options_factory,
)
) )
self._well_known_agent = _well_known_agent self._well_known_agent = _well_known_agent

View File

@ -166,7 +166,12 @@ def parse_string_from_args(
value = args[name][0] value = args[name][0]
if encoding: if encoding:
try:
value = value.decode(encoding) value = value.decode(encoding)
except ValueError:
raise SynapseError(
400, "Query parameter %r must be %s" % (name, encoding)
)
if allowed_values is not None and value not in allowed_values: if allowed_values is not None and value not in allowed_values:
message = "Query parameter %r must be one of [%s]" % ( message = "Query parameter %r must be one of [%s]" % (

View File

@ -245,7 +245,13 @@ BASE_APPEND_OVERRIDE_RULES = [
"key": "type", "key": "type",
"pattern": "m.room.tombstone", "pattern": "m.room.tombstone",
"_id": "_tombstone", "_id": "_tombstone",
} },
{
"kind": "event_match",
"key": "state_key",
"pattern": "",
"_id": "_tombstone_statekey",
},
], ],
"actions": ["notify", {"set_tweak": "highlight", "value": True}], "actions": ["notify", {"set_tweak": "highlight", "value": True}],
}, },

View File

@ -569,6 +569,27 @@ class RegistrationWorkerStore(SQLBaseStore):
desc="get_id_servers_user_bound", desc="get_id_servers_user_bound",
) )
@cachedInlineCallbacks()
def get_user_deactivated_status(self, user_id):
"""Retrieve the value for the `deactivated` property for the provided user.
Args:
user_id (str): The ID of the user to retrieve the status for.
Returns:
defer.Deferred(bool): The requested value.
"""
res = yield self._simple_select_one_onecol(
table="users",
keyvalues={"name": user_id},
retcol="deactivated",
desc="get_user_deactivated_status",
)
# Convert the integer into a boolean.
return res == 1
class RegistrationStore( class RegistrationStore(
RegistrationWorkerStore, background_updates.BackgroundUpdateStore RegistrationWorkerStore, background_updates.BackgroundUpdateStore
@ -1317,24 +1338,3 @@ class RegistrationStore(
user_id, user_id,
deactivated, deactivated,
) )
@cachedInlineCallbacks()
def get_user_deactivated_status(self, user_id):
"""Retrieve the value for the `deactivated` property for the provided user.
Args:
user_id (str): The ID of the user to retrieve the status for.
Returns:
defer.Deferred(bool): The requested value.
"""
res = yield self._simple_select_one_onecol(
table="users",
keyvalues={"name": user_id},
retcol="deactivated",
desc="get_user_deactivated_status",
)
# Convert the integer into a boolean.
return res == 1

View File

@ -75,7 +75,6 @@ class MatrixFederationAgentTests(TestCase):
config_dict = default_config("test", parse=False) config_dict = default_config("test", parse=False)
config_dict["federation_custom_ca_list"] = [get_test_ca_cert_file()] config_dict["federation_custom_ca_list"] = [get_test_ca_cert_file()]
# config_dict["trusted_key_servers"] = []
self._config = config = HomeServerConfig() self._config = config = HomeServerConfig()
config.parse_config_dict(config_dict, "", "") config.parse_config_dict(config_dict, "", "")
@ -83,7 +82,6 @@ class MatrixFederationAgentTests(TestCase):
self.agent = MatrixFederationAgent( self.agent = MatrixFederationAgent(
reactor=self.reactor, reactor=self.reactor,
tls_client_options_factory=ClientTLSOptionsFactory(config), tls_client_options_factory=ClientTLSOptionsFactory(config),
_well_known_tls_policy=TrustingTLSPolicyForHTTPS(),
_srv_resolver=self.mock_resolver, _srv_resolver=self.mock_resolver,
_well_known_cache=self.well_known_cache, _well_known_cache=self.well_known_cache,
) )
@ -691,16 +689,18 @@ class MatrixFederationAgentTests(TestCase):
not signed by a CA not signed by a CA
""" """
# we use the same test server as the other tests, but use an agent # we use the same test server as the other tests, but use an agent with
# with _well_known_tls_policy left to the default, which will not # the config left to the default, which will not trust it (since the
# trust it (since the presented cert is signed by a test CA) # presented cert is signed by a test CA)
self.mock_resolver.resolve_service.side_effect = lambda _: [] self.mock_resolver.resolve_service.side_effect = lambda _: []
self.reactor.lookups["testserv"] = "1.2.3.4" self.reactor.lookups["testserv"] = "1.2.3.4"
config = default_config("test", parse=True)
agent = MatrixFederationAgent( agent = MatrixFederationAgent(
reactor=self.reactor, reactor=self.reactor,
tls_client_options_factory=ClientTLSOptionsFactory(self._config), tls_client_options_factory=ClientTLSOptionsFactory(config),
_srv_resolver=self.mock_resolver, _srv_resolver=self.mock_resolver,
_well_known_cache=self.well_known_cache, _well_known_cache=self.well_known_cache,
) )

View File

@ -23,8 +23,6 @@ from mock import Mock
from canonicaljson import json from canonicaljson import json
import twisted
import twisted.logger
from twisted.internet.defer import Deferred, succeed from twisted.internet.defer import Deferred, succeed
from twisted.python.threadpool import ThreadPool from twisted.python.threadpool import ThreadPool
from twisted.trial import unittest from twisted.trial import unittest
@ -80,10 +78,6 @@ class TestCase(unittest.TestCase):
@around(self) @around(self)
def setUp(orig): def setUp(orig):
# enable debugging of delayed calls - this means that we get a
# traceback when a unit test exits leaving things on the reactor.
twisted.internet.base.DelayedCall.debug = True
# if we're not starting in the sentinel logcontext, then to be honest # if we're not starting in the sentinel logcontext, then to be honest
# all future bets are off. # all future bets are off.
if LoggingContext.current_context() is not LoggingContext.sentinel: if LoggingContext.current_context() is not LoggingContext.sentinel: