Synapse 1.21.0rc2 (2020-10-02)

==============================
 
 Features
 --------
 
 - Convert additional templates from inline HTML to Jinja2 templates. ([\#8444](https://github.com/matrix-org/synapse/issues/8444))
 
 Bugfixes
 --------
 
 - Fix a regression in v1.21.0rc1 which broke thumbnails of remote media. ([\#8438](https://github.com/matrix-org/synapse/issues/8438))
 - Do not expose the experimental `uk.half-shot.msc2778.login.application_service` flow in the login API, which caused a compatibility problem with Element iOS. ([\#8440](https://github.com/matrix-org/synapse/issues/8440))
 - Fix malformed log line in new federation "catch up" logic. ([\#8442](https://github.com/matrix-org/synapse/issues/8442))
 - Fix DB query on startup for negative streams which caused long start up times. Introduced in [\#8374](https://github.com/matrix-org/synapse/issues/8374). ([\#8447](https://github.com/matrix-org/synapse/issues/8447))
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEv27Axt/F4vrTL/8QOSor00I9eP8FAl93FccACgkQOSor00I9
 eP9/Egf7B4YOF6tniyAXxZvmvFOwV1WNw4sbFmF+czUKHBTAwS/Ij9MbutulD4OB
 +yqHAvu15qUCQR/G+KGjyHBDtESEUtn5SRy8znLYlR2n3qfEdEpd5y6LJSq4s7sr
 NjFVNVI1g5L8PmbvvWCINfpPm2JSm8zyOdyxy4KZifex1B+8YgPILeQOB59sWL/H
 1maFbHCgepqO3jotsA8PUXQZx5oScABmqYYe92b4sLna00uFBq2NWp0NA654dRqK
 VRFlGzId1fZNWTy1jzfOY2sJKpBCy4cMrtfGJ/eqMtryHqbnBFT6hgB8FyTNg0h0
 oew+BLV/mcJLcvB0ALRMFS7xZHdoxQ==
 =+3N3
 -----END PGP SIGNATURE-----

Merge tag 'v1.21.0rc2' into develop

Synapse 1.21.0rc2 (2020-10-02)
==============================

Features
--------

- Convert additional templates from inline HTML to Jinja2 templates. ([\#8444](https://github.com/matrix-org/synapse/issues/8444))

Bugfixes
--------

- Fix a regression in v1.21.0rc1 which broke thumbnails of remote media. ([\#8438](https://github.com/matrix-org/synapse/issues/8438))
- Do not expose the experimental `uk.half-shot.msc2778.login.application_service` flow in the login API, which caused a compatibility problem with Element iOS. ([\#8440](https://github.com/matrix-org/synapse/issues/8440))
- Fix malformed log line in new federation "catch up" logic. ([\#8442](https://github.com/matrix-org/synapse/issues/8442))
- Fix DB query on startup for negative streams which caused long start up times. Introduced in [\#8374](https://github.com/matrix-org/synapse/issues/8374). ([\#8447](https://github.com/matrix-org/synapse/issues/8447))
This commit is contained in:
Richard van der Hoff 2020-10-02 12:59:17 +01:00
commit 462e681c79
14 changed files with 163 additions and 141 deletions

View file

@ -1,3 +1,20 @@
Synapse 1.21.0rc2 (2020-10-02)
==============================
Features
--------
- Convert additional templates from inline HTML to Jinja2 templates. ([\#8444](https://github.com/matrix-org/synapse/issues/8444))
Bugfixes
--------
- Fix a regression in v1.21.0rc1 which broke thumbnails of remote media. ([\#8438](https://github.com/matrix-org/synapse/issues/8438))
- Do not expose the experimental `uk.half-shot.msc2778.login.application_service` flow in the login API, which caused a compatibility problem with Element iOS. ([\#8440](https://github.com/matrix-org/synapse/issues/8440))
- Fix malformed log line in new federation "catch up" logic. ([\#8442](https://github.com/matrix-org/synapse/issues/8442))
- Fix DB query on startup for negative streams which caused long start up times. Introduced in [\#8374](https://github.com/matrix-org/synapse/issues/8374). ([\#8447](https://github.com/matrix-org/synapse/issues/8447))
Synapse 1.21.0rc1 (2020-10-01) Synapse 1.21.0rc1 (2020-10-01)
============================== ==============================

View file

@ -48,7 +48,7 @@ try:
except ImportError: except ImportError:
pass pass
__version__ = "1.21.0rc1" __version__ = "1.21.0rc2"
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)): if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
# We import here so that we don't have to install a bunch of deps when # We import here so that we don't have to install a bunch of deps when

View file

@ -242,12 +242,11 @@ class Config:
env = jinja2.Environment(loader=loader, autoescape=autoescape) env = jinja2.Environment(loader=loader, autoescape=autoescape)
# Update the environment with our custom filters # Update the environment with our custom filters
env.filters.update( env.filters.update({"format_ts": _format_ts_filter})
{ if self.public_baseurl:
"format_ts": _format_ts_filter, env.filters.update(
"mxc_to_http": _create_mxc_to_http_filter(self.public_baseurl), {"mxc_to_http": _create_mxc_to_http_filter(self.public_baseurl)}
} )
)
for filename in filenames: for filename in filenames:
# Load the template # Load the template

View file

@ -28,6 +28,9 @@ class CaptchaConfig(Config):
"recaptcha_siteverify_api", "recaptcha_siteverify_api",
"https://www.recaptcha.net/recaptcha/api/siteverify", "https://www.recaptcha.net/recaptcha/api/siteverify",
) )
self.recaptcha_template = self.read_templates(
["recaptcha.html"], autoescape=True
)[0]
def generate_config_section(self, **kwargs): def generate_config_section(self, **kwargs):
return """\ return """\

View file

@ -89,6 +89,8 @@ class ConsentConfig(Config):
def read_config(self, config, **kwargs): def read_config(self, config, **kwargs):
consent_config = config.get("user_consent") consent_config = config.get("user_consent")
self.terms_template = self.read_templates(["terms.html"], autoescape=True)[0]
if consent_config is None: if consent_config is None:
return return
self.user_consent_version = str(consent_config["version"]) self.user_consent_version = str(consent_config["version"])

View file

@ -187,6 +187,11 @@ class RegistrationConfig(Config):
session_lifetime = self.parse_duration(session_lifetime) session_lifetime = self.parse_duration(session_lifetime)
self.session_lifetime = session_lifetime self.session_lifetime = session_lifetime
# The success template used during fallback auth.
self.fallback_success_template = self.read_templates(
["auth_success.html"], autoescape=True
)[0]
def generate_config_section(self, generate_secrets=False, **kwargs): def generate_config_section(self, generate_secrets=False, **kwargs):
if generate_secrets: if generate_secrets:
registration_shared_secret = 'registration_shared_secret: "%s"' % ( registration_shared_secret = 'registration_shared_secret: "%s"' % (

View file

@ -490,7 +490,7 @@ class PerDestinationQueue:
) )
if logger.isEnabledFor(logging.INFO): if logger.isEnabledFor(logging.INFO):
rooms = (p.room_id for p in catchup_pdus) rooms = [p.room_id for p in catchup_pdus]
logger.info("Catching up rooms to %s: %r", self._destination, rooms) logger.info("Catching up rooms to %s: %r", self._destination, rooms)
success = await self._transaction_manager.send_new_transaction( success = await self._transaction_manager.send_new_transaction(

View file

@ -0,0 +1,21 @@
<html>
<head>
<title>Success!</title>
<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="/_matrix/static/client/register/style.css">
<script>
if (window.onAuthDone) {
window.onAuthDone();
} else if (window.opener && window.opener.postMessage) {
window.opener.postMessage("authDone", "*");
}
</script>
</head>
<body>
<div>
<p>Thank you</p>
<p>You may now close this window and return to the application</p>
</div>
</body>
</html>

View file

@ -0,0 +1,38 @@
<html>
<head>
<title>Authentication</title>
<meta name='viewport' content='width=device-width, initial-scale=1,
user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
<script src="https://www.recaptcha.net/recaptcha/api.js"
async defer></script>
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<link rel="stylesheet" href="/_matrix/static/client/register/style.css">
<script>
function captchaDone() {
$('#registrationForm').submit();
}
</script>
</head>
<body>
<form id="registrationForm" method="post" action="{{ myurl }}">
<div>
<p>
Hello! We need to prevent computer programs and other automated
things from creating accounts on this server.
</p>
<p>
Please verify that you're not a robot.
</p>
<input type="hidden" name="session" value="{{ session }}" />
<div class="g-recaptcha"
data-sitekey="{{ sitekey }}"
data-callback="captchaDone">
</div>
<noscript>
<input type="submit" value="All Done" />
</noscript>
</div>
</div>
</form>
</body>
</html>

View file

@ -0,0 +1,20 @@
<html>
<head>
<title>Authentication</title>
<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="/_matrix/static/client/register/style.css">
</head>
<body>
<form id="registrationForm" method="post" action="{{ myurl }}">
<div>
<p>
Please click the button below if you agree to the
<a href="{{ terms_url }}">privacy policy of this homeserver.</a>
</p>
<input type="hidden" name="session" value="{{ session }}" />
<input type="submit" value="Agree" />
</div>
</form>
</body>
</html>

View file

@ -111,8 +111,6 @@ class LoginRestServlet(RestServlet):
({"type": t} for t in self.auth_handler.get_supported_login_types()) ({"type": t} for t in self.auth_handler.get_supported_login_types())
) )
flows.append({"type": LoginRestServlet.APPSERVICE_TYPE})
return 200, {"flows": flows} return 200, {"flows": flows}
def on_OPTIONS(self, request: SynapseRequest): def on_OPTIONS(self, request: SynapseRequest):

View file

@ -25,94 +25,6 @@ from ._base import client_patterns
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
RECAPTCHA_TEMPLATE = """
<html>
<head>
<title>Authentication</title>
<meta name='viewport' content='width=device-width, initial-scale=1,
user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
<script src="https://www.recaptcha.net/recaptcha/api.js"
async defer></script>
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<link rel="stylesheet" href="/_matrix/static/client/register/style.css">
<script>
function captchaDone() {
$('#registrationForm').submit();
}
</script>
</head>
<body>
<form id="registrationForm" method="post" action="%(myurl)s">
<div>
<p>
Hello! We need to prevent computer programs and other automated
things from creating accounts on this server.
</p>
<p>
Please verify that you're not a robot.
</p>
<input type="hidden" name="session" value="%(session)s" />
<div class="g-recaptcha"
data-sitekey="%(sitekey)s"
data-callback="captchaDone">
</div>
<noscript>
<input type="submit" value="All Done" />
</noscript>
</div>
</div>
</form>
</body>
</html>
"""
TERMS_TEMPLATE = """
<html>
<head>
<title>Authentication</title>
<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="/_matrix/static/client/register/style.css">
</head>
<body>
<form id="registrationForm" method="post" action="%(myurl)s">
<div>
<p>
Please click the button below if you agree to the
<a href="%(terms_url)s">privacy policy of this homeserver.</a>
</p>
<input type="hidden" name="session" value="%(session)s" />
<input type="submit" value="Agree" />
</div>
</form>
</body>
</html>
"""
SUCCESS_TEMPLATE = """
<html>
<head>
<title>Success!</title>
<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="/_matrix/static/client/register/style.css">
<script>
if (window.onAuthDone) {
window.onAuthDone();
} else if (window.opener && window.opener.postMessage) {
window.opener.postMessage("authDone", "*");
}
</script>
</head>
<body>
<div>
<p>Thank you</p>
<p>You may now close this window and return to the application</p>
</div>
</body>
</html>
"""
class AuthRestServlet(RestServlet): class AuthRestServlet(RestServlet):
""" """
@ -145,26 +57,30 @@ class AuthRestServlet(RestServlet):
self._cas_server_url = hs.config.cas_server_url self._cas_server_url = hs.config.cas_server_url
self._cas_service_url = hs.config.cas_service_url self._cas_service_url = hs.config.cas_service_url
self.recaptcha_template = hs.config.recaptcha_template
self.terms_template = hs.config.terms_template
self.success_template = hs.config.fallback_success_template
async def on_GET(self, request, stagetype): async def on_GET(self, request, stagetype):
session = parse_string(request, "session") session = parse_string(request, "session")
if not session: if not session:
raise SynapseError(400, "No session supplied") raise SynapseError(400, "No session supplied")
if stagetype == LoginType.RECAPTCHA: if stagetype == LoginType.RECAPTCHA:
html = RECAPTCHA_TEMPLATE % { html = self.recaptcha_template.render(
"session": session, session=session,
"myurl": "%s/r0/auth/%s/fallback/web" myurl="%s/r0/auth/%s/fallback/web"
% (CLIENT_API_PREFIX, LoginType.RECAPTCHA), % (CLIENT_API_PREFIX, LoginType.RECAPTCHA),
"sitekey": self.hs.config.recaptcha_public_key, sitekey=self.hs.config.recaptcha_public_key,
} )
elif stagetype == LoginType.TERMS: elif stagetype == LoginType.TERMS:
html = TERMS_TEMPLATE % { html = self.terms_template.render(
"session": session, session=session,
"terms_url": "%s_matrix/consent?v=%s" terms_url="%s_matrix/consent?v=%s"
% (self.hs.config.public_baseurl, self.hs.config.user_consent_version), % (self.hs.config.public_baseurl, self.hs.config.user_consent_version),
"myurl": "%s/r0/auth/%s/fallback/web" myurl="%s/r0/auth/%s/fallback/web"
% (CLIENT_API_PREFIX, LoginType.TERMS), % (CLIENT_API_PREFIX, LoginType.TERMS),
} )
elif stagetype == LoginType.SSO: elif stagetype == LoginType.SSO:
# Display a confirmation page which prompts the user to # Display a confirmation page which prompts the user to
@ -222,14 +138,14 @@ class AuthRestServlet(RestServlet):
) )
if success: if success:
html = SUCCESS_TEMPLATE html = self.success_template.render()
else: else:
html = RECAPTCHA_TEMPLATE % { html = self.recaptcha_template.render(
"session": session, session=session,
"myurl": "%s/r0/auth/%s/fallback/web" myurl="%s/r0/auth/%s/fallback/web"
% (CLIENT_API_PREFIX, LoginType.RECAPTCHA), % (CLIENT_API_PREFIX, LoginType.RECAPTCHA),
"sitekey": self.hs.config.recaptcha_public_key, sitekey=self.hs.config.recaptcha_public_key,
} )
elif stagetype == LoginType.TERMS: elif stagetype == LoginType.TERMS:
authdict = {"session": session} authdict = {"session": session}
@ -238,18 +154,18 @@ class AuthRestServlet(RestServlet):
) )
if success: if success:
html = SUCCESS_TEMPLATE html = self.success_template.render()
else: else:
html = TERMS_TEMPLATE % { html = self.terms_template.render(
"session": session, session=session,
"terms_url": "%s_matrix/consent?v=%s" terms_url="%s_matrix/consent?v=%s"
% ( % (
self.hs.config.public_baseurl, self.hs.config.public_baseurl,
self.hs.config.user_consent_version, self.hs.config.user_consent_version,
), ),
"myurl": "%s/r0/auth/%s/fallback/web" myurl="%s/r0/auth/%s/fallback/web"
% (CLIENT_API_PREFIX, LoginType.TERMS), % (CLIENT_API_PREFIX, LoginType.TERMS),
} )
elif stagetype == LoginType.SSO: elif stagetype == LoginType.SSO:
# The SSO fallback workflow should not post here, # The SSO fallback workflow should not post here,
raise SynapseError(404, "Fallback SSO auth does not support POST requests.") raise SynapseError(404, "Fallback SSO auth does not support POST requests.")

View file

@ -141,31 +141,34 @@ class MediaStorage:
Returns: Returns:
Returns a Responder if the file was found, otherwise None. Returns a Responder if the file was found, otherwise None.
""" """
paths = [self._file_info_to_path(file_info)]
path = self._file_info_to_path(file_info) # fallback for remote thumbnails with no method in the filename
local_path = os.path.join(self.local_media_directory, path)
if os.path.exists(local_path):
return FileResponder(open(local_path, "rb"))
# Fallback for paths without method names
# Should be removed in the future
if file_info.thumbnail and file_info.server_name: if file_info.thumbnail and file_info.server_name:
legacy_path = self.filepaths.remote_media_thumbnail_rel_legacy( paths.append(
server_name=file_info.server_name, self.filepaths.remote_media_thumbnail_rel_legacy(
file_id=file_info.file_id, server_name=file_info.server_name,
width=file_info.thumbnail_width, file_id=file_info.file_id,
height=file_info.thumbnail_height, width=file_info.thumbnail_width,
content_type=file_info.thumbnail_type, height=file_info.thumbnail_height,
content_type=file_info.thumbnail_type,
)
) )
legacy_local_path = os.path.join(self.local_media_directory, legacy_path)
if os.path.exists(legacy_local_path): for path in paths:
return FileResponder(open(legacy_local_path, "rb")) local_path = os.path.join(self.local_media_directory, path)
if os.path.exists(local_path):
logger.debug("responding with local file %s", local_path)
return FileResponder(open(local_path, "rb"))
logger.debug("local file %s did not exist", local_path)
for provider in self.storage_providers: for provider in self.storage_providers:
res = await provider.fetch(path, file_info) # type: Any for path in paths:
if res: res = await provider.fetch(path, file_info) # type: Any
logger.debug("Streaming %s from %s", path, provider) if res:
return res logger.debug("Streaming %s from %s", path, provider)
return res
logger.debug("%s not found on %s", path, provider)
return None return None

View file

@ -341,7 +341,7 @@ class MultiWriterIdGenerator:
"cmp": "<=" if self._positive else ">=", "cmp": "<=" if self._positive else ">=",
} }
sql = self._db.engine.convert_param_style(sql) sql = self._db.engine.convert_param_style(sql)
cur.execute(sql, (min_stream_id,)) cur.execute(sql, (min_stream_id * self._return_factor,))
self._persisted_upto_position = min_stream_id self._persisted_upto_position = min_stream_id