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

pull/4767/head
Erik Johnston 2019-02-26 09:36:29 +00:00
commit 75c924430e
22 changed files with 161 additions and 49 deletions

View File

@ -199,6 +199,8 @@ by installing the ``libjemalloc1`` package and adding this line to
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1
This can make a significant difference on Python 2.7 - it's unclear how
much of an improvement it provides on Python 3.x.
Upgrading an existing Synapse Upgrading an existing Synapse
============================= =============================

View File

@ -1 +1 @@
Cleanup request exception logging Cleanup request exception logging.

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

@ -0,0 +1 @@
Fix ACME config for python 2.

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

@ -0,0 +1 @@
Fix paginating over federation persisting incorrect state.

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

@ -0,0 +1 @@
Return correct error code when inviting a remote user to a room whose homeserver does not support the room version.

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

@ -0,0 +1 @@
Don't log exceptions when failing to fetch remote server keys

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

@ -0,0 +1 @@
Correctly proxy exception in frontend_proxy worker

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

@ -0,0 +1 @@
Cleanup request exception logging.

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

@ -0,0 +1 @@
Cleanup request exception logging.

View File

@ -21,7 +21,7 @@ from twisted.web.resource import NoResource
import synapse import synapse
from synapse import events from synapse import events
from synapse.api.errors import SynapseError from synapse.api.errors import HttpResponseException, SynapseError
from synapse.app import _base from synapse.app import _base
from synapse.config._base import ConfigError from synapse.config._base import ConfigError
from synapse.config.homeserver import HomeServerConfig from synapse.config.homeserver import HomeServerConfig
@ -66,10 +66,15 @@ class PresenceStatusStubServlet(ClientV1RestServlet):
headers = { headers = {
"Authorization": auth_headers, "Authorization": auth_headers,
} }
result = yield self.http_client.get_json(
self.main_uri + request.uri.decode('ascii'), try:
headers=headers, result = yield self.http_client.get_json(
) self.main_uri + request.uri.decode('ascii'),
headers=headers,
)
except HttpResponseException as e:
raise e.to_synapse_error()
defer.returnValue((200, result)) defer.returnValue((200, result))
@defer.inlineCallbacks @defer.inlineCallbacks

View File

@ -47,5 +47,5 @@ class CaptchaConfig(Config):
#captcha_bypass_secret: "YOUR_SECRET_HERE" #captcha_bypass_secret: "YOUR_SECRET_HERE"
# The API endpoint to use for verifying m.login.recaptcha responses. # The API endpoint to use for verifying m.login.recaptcha responses.
recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify" recaptcha_siteverify_api: "https://www.recaptcha.net/recaptcha/api/siteverify"
""" """

View File

@ -19,6 +19,8 @@ import warnings
from datetime import datetime from datetime import datetime
from hashlib import sha256 from hashlib import sha256
import six
from unpaddedbase64 import encode_base64 from unpaddedbase64 import encode_base64
from OpenSSL import crypto from OpenSSL import crypto
@ -36,9 +38,11 @@ class TlsConfig(Config):
acme_config = {} acme_config = {}
self.acme_enabled = acme_config.get("enabled", False) self.acme_enabled = acme_config.get("enabled", False)
self.acme_url = acme_config.get(
# hyperlink complains on py2 if this is not a Unicode
self.acme_url = six.text_type(acme_config.get(
"url", u"https://acme-v01.api.letsencrypt.org/directory" "url", u"https://acme-v01.api.letsencrypt.org/directory"
) ))
self.acme_port = acme_config.get("port", 80) self.acme_port = acme_config.get("port", 80)
self.acme_bind_addresses = acme_config.get("bind_addresses", ['::', '0.0.0.0']) self.acme_bind_addresses = acme_config.get("bind_addresses", ['::', '0.0.0.0'])
self.acme_reprovision_threshold = acme_config.get("reprovision_threshold", 30) self.acme_reprovision_threshold = acme_config.get("reprovision_threshold", 30)
@ -55,7 +59,7 @@ class TlsConfig(Config):
) )
if not self.tls_private_key_file: if not self.tls_private_key_file:
raise ConfigError( raise ConfigError(
"tls_certificate_path must be specified if TLS-enabled listeners are " "tls_private_key_path must be specified if TLS-enabled listeners are "
"configured." "configured."
) )

View File

@ -17,6 +17,7 @@
import logging import logging
from collections import namedtuple from collections import namedtuple
from six import raise_from
from six.moves import urllib from six.moves import urllib
from signedjson.key import ( from signedjson.key import (
@ -35,7 +36,12 @@ from unpaddedbase64 import decode_base64
from twisted.internet import defer from twisted.internet import defer
from synapse.api.errors import Codes, RequestSendFailed, SynapseError from synapse.api.errors import (
Codes,
HttpResponseException,
RequestSendFailed,
SynapseError,
)
from synapse.util import logcontext, unwrapFirstError from synapse.util import logcontext, unwrapFirstError
from synapse.util.logcontext import ( from synapse.util.logcontext import (
LoggingContext, LoggingContext,
@ -44,6 +50,7 @@ from synapse.util.logcontext import (
run_in_background, run_in_background,
) )
from synapse.util.metrics import Measure from synapse.util.metrics import Measure
from synapse.util.retryutils import NotRetryingDestination
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -367,13 +374,18 @@ class Keyring(object):
server_name_and_key_ids, perspective_name, perspective_keys server_name_and_key_ids, perspective_name, perspective_keys
) )
defer.returnValue(result) defer.returnValue(result)
except KeyLookupError as e:
logger.warning(
"Key lookup failed from %r: %s", perspective_name, e,
)
except Exception as e: except Exception as e:
logger.exception( logger.exception(
"Unable to get key from %r: %s %s", "Unable to get key from %r: %s %s",
perspective_name, perspective_name,
type(e).__name__, str(e), type(e).__name__, str(e),
) )
defer.returnValue({})
defer.returnValue({})
results = yield logcontext.make_deferred_yieldable(defer.gatherResults( results = yield logcontext.make_deferred_yieldable(defer.gatherResults(
[ [
@ -421,21 +433,30 @@ class Keyring(object):
# TODO(mark): Set the minimum_valid_until_ts to that needed by # TODO(mark): Set the minimum_valid_until_ts to that needed by
# the events being validated or the current time if validating # the events being validated or the current time if validating
# an incoming request. # an incoming request.
query_response = yield self.client.post_json( try:
destination=perspective_name, query_response = yield self.client.post_json(
path="/_matrix/key/v2/query", destination=perspective_name,
data={ path="/_matrix/key/v2/query",
u"server_keys": { data={
server_name: { u"server_keys": {
key_id: { server_name: {
u"minimum_valid_until_ts": 0 key_id: {
} for key_id in key_ids u"minimum_valid_until_ts": 0
} for key_id in key_ids
}
for server_name, key_ids in server_names_and_key_ids
} }
for server_name, key_ids in server_names_and_key_ids },
} long_retries=True,
}, )
long_retries=True, except (NotRetryingDestination, RequestSendFailed) as e:
) raise_from(
KeyLookupError("Failed to connect to remote server"), e,
)
except HttpResponseException as e:
raise_from(
KeyLookupError("Remote server returned an error"), e,
)
keys = {} keys = {}
@ -502,11 +523,20 @@ class Keyring(object):
if requested_key_id in keys: if requested_key_id in keys:
continue continue
response = yield self.client.get_json( try:
destination=server_name, response = yield self.client.get_json(
path="/_matrix/key/v2/server/" + urllib.parse.quote(requested_key_id), destination=server_name,
ignore_backoff=True, path="/_matrix/key/v2/server/" + urllib.parse.quote(requested_key_id),
) ignore_backoff=True,
)
except (NotRetryingDestination, RequestSendFailed) as e:
raise_from(
KeyLookupError("Failed to connect to remote server"), e,
)
except HttpResponseException as e:
raise_from(
KeyLookupError("Remote server returned an error"), e,
)
if (u"signatures" not in response if (u"signatures" not in response
or server_name not in response[u"signatures"]): or server_name not in response[u"signatures"]):

View File

@ -33,6 +33,7 @@ from synapse.api.constants import (
) )
from synapse.api.errors import ( from synapse.api.errors import (
CodeMessageException, CodeMessageException,
Codes,
FederationDeniedError, FederationDeniedError,
HttpResponseException, HttpResponseException,
SynapseError, SynapseError,
@ -792,10 +793,25 @@ class FederationClient(FederationBase):
defer.returnValue(content) defer.returnValue(content)
except HttpResponseException as e: except HttpResponseException as e:
if e.code in [400, 404]: if e.code in [400, 404]:
err = e.to_synapse_error()
# If we receive an error response that isn't a generic error, we
# assume that the remote understands the v2 invite API and this
# is a legitimate error.
if err.errcode != Codes.UNKNOWN:
raise err
# Otherwise, we assume that the remote server doesn't understand
# the v2 invite API.
if room_version in (RoomVersions.V1, RoomVersions.V2): if room_version in (RoomVersions.V1, RoomVersions.V2):
pass # We'll fall through pass # We'll fall through
else: else:
raise Exception("Remote server is too old") raise SynapseError(
400,
"User's homeserver does not support this room version",
Codes.UNSUPPORTED_ROOM_VERSION,
)
elif e.code == 403: elif e.code == 403:
raise e.to_synapse_error() raise e.to_synapse_error()
else: else:

View File

@ -25,9 +25,10 @@ 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, Membership from synapse.api.constants import KNOWN_ROOM_VERSIONS, EventTypes, Membership
from synapse.api.errors import ( from synapse.api.errors import (
AuthError, AuthError,
Codes,
FederationError, FederationError,
IncompatibleRoomVersionError, IncompatibleRoomVersionError,
NotFoundError, NotFoundError,
@ -239,8 +240,9 @@ class FederationServer(FederationBase):
f = failure.Failure() f = failure.Failure()
pdu_results[event_id] = {"error": str(e)} pdu_results[event_id] = {"error": str(e)}
logger.error( logger.error(
"Failed to handle PDU %s: %s", "Failed to handle PDU %s",
event_id, f.getTraceback().rstrip(), event_id,
exc_info=(f.type, f.value, f.getTracebackObject()),
) )
yield concurrently_execute( yield concurrently_execute(
@ -386,6 +388,13 @@ class FederationServer(FederationBase):
@defer.inlineCallbacks @defer.inlineCallbacks
def on_invite_request(self, origin, content, room_version): def on_invite_request(self, origin, content, room_version):
if room_version not in KNOWN_ROOM_VERSIONS:
raise SynapseError(
400,
"Homeserver does not support this room version",
Codes.UNSUPPORTED_ROOM_VERSION,
)
format_ver = room_version_to_event_format(room_version) format_ver = room_version_to_event_format(room_version)
pdu = event_from_pdu_json(content, format_ver) pdu = event_from_pdu_json(content, format_ver)

View File

@ -770,10 +770,26 @@ class FederationHandler(BaseHandler):
set(auth_events.keys()) | set(state_events.keys()) set(auth_events.keys()) | set(state_events.keys())
) )
# We now have a chunk of events plus associated state and auth chain to
# persist. We do the persistence in two steps:
# 1. Auth events and state get persisted as outliers, plus the
# backward extremities get persisted (as non-outliers).
# 2. The rest of the events in the chunk get persisted one by one, as
# each one depends on the previous event for its state.
#
# The important thing is that events in the chunk get persisted as
# non-outliers, including when those events are also in the state or
# auth chain. Caution must therefore be taken to ensure that they are
# not accidentally marked as outliers.
# Step 1a: persist auth events that *don't* appear in the chunk
ev_infos = [] ev_infos = []
for a in auth_events.values(): for a in auth_events.values():
if a.event_id in seen_events: # We only want to persist auth events as outliers that we haven't
# seen and aren't about to persist as part of the backfilled chunk.
if a.event_id in seen_events or a.event_id in event_map:
continue continue
a.internal_metadata.outlier = True a.internal_metadata.outlier = True
ev_infos.append({ ev_infos.append({
"event": a, "event": a,
@ -785,14 +801,21 @@ class FederationHandler(BaseHandler):
} }
}) })
# Step 1b: persist the events in the chunk we fetched state for (i.e.
# the backwards extremities) as non-outliers.
for e_id in events_to_state: for e_id in events_to_state:
# For paranoia we ensure that these events are marked as
# non-outliers
ev = event_map[e_id]
assert(not ev.internal_metadata.is_outlier())
ev_infos.append({ ev_infos.append({
"event": event_map[e_id], "event": ev,
"state": events_to_state[e_id], "state": events_to_state[e_id],
"auth_events": { "auth_events": {
(auth_events[a_id].type, auth_events[a_id].state_key): (auth_events[a_id].type, auth_events[a_id].state_key):
auth_events[a_id] auth_events[a_id]
for a_id in event_map[e_id].auth_event_ids() for a_id in ev.auth_event_ids()
if a_id in auth_events if a_id in auth_events
} }
}) })
@ -802,12 +825,17 @@ class FederationHandler(BaseHandler):
backfilled=True, backfilled=True,
) )
# Step 2: Persist the rest of the events in the chunk one by one
events.sort(key=lambda e: e.depth) events.sort(key=lambda e: e.depth)
for event in events: for event in events:
if event in events_to_state: if event in events_to_state:
continue continue
# For paranoia we ensure that these events are marked as
# non-outliers
assert(not event.internal_metadata.is_outlier())
# We store these one at a time since each event depends on the # We store these one at a time since each event depends on the
# previous to work out the state. # previous to work out the state.
# TODO: We can probably do something more clever here. # TODO: We can probably do something more clever here.

View File

@ -136,7 +136,11 @@ class PaginationHandler(object):
logger.info("[purge] complete") logger.info("[purge] complete")
self._purges_by_id[purge_id].status = PurgeStatus.STATUS_COMPLETE self._purges_by_id[purge_id].status = PurgeStatus.STATUS_COMPLETE
except Exception: except Exception:
logger.error("[purge] failed: %s", Failure().getTraceback().rstrip()) f = Failure()
logger.error(
"[purge] failed",
exc_info=(f.type, f.value, f.getTracebackObject()),
)
self._purges_by_id[purge_id].status = PurgeStatus.STATUS_FAILED self._purges_by_id[purge_id].status = PurgeStatus.STATUS_FAILED
finally: finally:
self._purges_in_progress_by_room.discard(room_id) self._purges_in_progress_by_room.discard(room_id)

View File

@ -460,7 +460,7 @@ class RegistrationHandler(BaseHandler):
lines = response.split('\n') lines = response.split('\n')
json = { json = {
"valid": lines[0] == 'true', "valid": lines[0] == 'true',
"error_url": "http://www.google.com/recaptcha/api/challenge?" + "error_url": "http://www.recaptcha.net/recaptcha/api/challenge?" +
"error=%s" % lines[1] "error=%s" % lines[1]
} }
defer.returnValue(json) defer.returnValue(json)
@ -471,7 +471,7 @@ class RegistrationHandler(BaseHandler):
Used only by c/s api v1 Used only by c/s api v1
""" """
data = yield self.captcha_client.post_urlencoded_get_raw( data = yield self.captcha_client.post_urlencoded_get_raw(
"http://www.google.com:80/recaptcha/api/verify", "http://www.recaptcha.net:80/recaptcha/api/verify",
args={ args={
'privatekey': private_key, 'privatekey': private_key,
'remoteip': ip_addr, 'remoteip': ip_addr,

View File

@ -169,18 +169,18 @@ def _return_html_error(f, request):
) )
else: else:
logger.error( logger.error(
"Failed handle request %r: %s", "Failed handle request %r",
request, request,
f.getTraceback().rstrip(), exc_info=(f.type, f.value, f.getTracebackObject()),
) )
else: else:
code = http_client.INTERNAL_SERVER_ERROR code = http_client.INTERNAL_SERVER_ERROR
msg = "Internal server error" msg = "Internal server error"
logger.error( logger.error(
"Failed handle request %r: %s", "Failed handle request %r",
request, request,
f.getTraceback().rstrip(), exc_info=(f.type, f.value, f.getTracebackObject()),
) )
body = HTML_ERROR_TEMPLATE.format( body = HTML_ERROR_TEMPLATE.format(

View File

@ -33,7 +33,7 @@ RECAPTCHA_TEMPLATE = """
<title>Authentication</title> <title>Authentication</title>
<meta name='viewport' content='width=device-width, initial-scale=1, <meta name='viewport' content='width=device-width, initial-scale=1,
user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'> user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
<script src="https://www.google.com/recaptcha/api.js" <script src="https://www.recaptcha.net/recaptcha/api.js"
async defer></script> async defer></script>
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script> <script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<link rel="stylesheet" href="/_matrix/static/client/register/style.css"> <link rel="stylesheet" href="/_matrix/static/client/register/style.css">

View File

@ -133,8 +133,15 @@ def respond_with_responder(request, responder, media_type, file_size, upload_nam
logger.debug("Responding to media request with responder %s") logger.debug("Responding to media request with responder %s")
add_file_headers(request, media_type, file_size, upload_name) add_file_headers(request, media_type, file_size, upload_name)
with responder: try:
yield responder.write_to_consumer(request) with responder:
yield responder.write_to_consumer(request)
except Exception as e:
# The majority of the time this will be due to the client having gone
# away. Unfortunately, Twisted simply throws a generic exception at us
# in that case.
logger.warning("Failed to write to consumer: %s %s", type(e), e)
finish_request(request) finish_request(request)

View File

@ -4,7 +4,7 @@
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'> <meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="js/jquery-2.1.3.min.js"></script> <script src="js/jquery-2.1.3.min.js"></script>
<script src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script> <script src="https://www.recaptcha.net/recaptcha/api/js/recaptcha_ajax.js"></script>
<script src="register_config.js"></script> <script src="register_config.js"></script>
<script src="js/register.js"></script> <script src="js/register.js"></script>
</head> </head>