Clean-up the template loading code. (#9200)

* Enables autoescape by default for HTML files.
* Adds a new read_template method for reading a single template.
* Some logic clean-up.
This commit is contained in:
Patrick Cloke 2021-01-27 10:59:50 -05:00 committed by GitHub
parent 93b61589b0
commit e54746bdf7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 96 additions and 38 deletions

View file

@ -85,6 +85,43 @@ for example:
wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb
dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb
Upgrading to v1.27.0
====================
Changes to HTML templates
-------------------------
The HTML templates for SSO and email notifications now have `Jinja2's autoescape <https://jinja.palletsprojects.com/en/2.11.x/api/#autoescaping>`_
enabled for files ending in ``.html``, ``.htm``, and ``.xml``. If you hae customised
these templates and see issues when viewing them you might need to update them.
It is expected that most configurations will need no changes.
If you have customised the templates *names* for these templates it is recommended
to verify they end in ``.html`` to ensure autoescape is enabled.
The above applies to the following templates:
* ``add_threepid.html``
* ``add_threepid_failure.html``
* ``add_threepid_success.html``
* ``notice_expiry.html``
* ``notice_expiry.html``
* ``notif_mail.html`` (which, by default, includes ``room.html`` and ``notif.html``)
* ``password_reset.html``
* ``password_reset_confirmation.html``
* ``password_reset_failure.html``
* ``password_reset_success.html``
* ``registration.html``
* ``registration_failure.html``
* ``registration_success.html``
* ``sso_account_deactivated.html``
* ``sso_auth_bad_user.html``
* ``sso_auth_confirm.html``
* ``sso_auth_success.html``
* ``sso_error.html``
* ``sso_login_idp_picker.html``
* ``sso_redirect_confirm.html``
Upgrading to v1.26.0 Upgrading to v1.26.0
==================== ====================

1
changelog.d/9200.misc Normal file
View file

@ -0,0 +1 @@
Clean-up template loading code.

View file

@ -203,11 +203,28 @@ class Config:
with open(file_path) as file_stream: with open(file_path) as file_stream:
return file_stream.read() return file_stream.read()
def read_template(self, filename: str) -> jinja2.Template:
"""Load a template file from disk.
This function will attempt to load the given template from the default Synapse
template directory.
Files read are treated as Jinja templates. The templates is not rendered yet
and has autoescape enabled.
Args:
filename: A template filename to read.
Raises:
ConfigError: if the file's path is incorrect or otherwise cannot be read.
Returns:
A jinja2 template.
"""
return self.read_templates([filename])[0]
def read_templates( def read_templates(
self, self, filenames: List[str], custom_template_directory: Optional[str] = None,
filenames: List[str],
custom_template_directory: Optional[str] = None,
autoescape: bool = False,
) -> List[jinja2.Template]: ) -> List[jinja2.Template]:
"""Load a list of template files from disk using the given variables. """Load a list of template files from disk using the given variables.
@ -215,7 +232,8 @@ class Config:
template directory. If `custom_template_directory` is supplied, that directory template directory. If `custom_template_directory` is supplied, that directory
is tried first. is tried first.
Files read are treated as Jinja templates. These templates are not rendered yet. Files read are treated as Jinja templates. The templates are not rendered yet
and have autoescape enabled.
Args: Args:
filenames: A list of template filenames to read. filenames: A list of template filenames to read.
@ -223,16 +241,12 @@ class Config:
custom_template_directory: A directory to try to look for the templates custom_template_directory: A directory to try to look for the templates
before using the default Synapse template directory instead. before using the default Synapse template directory instead.
autoescape: Whether to autoescape variables before inserting them into the
template.
Raises: Raises:
ConfigError: if the file's path is incorrect or otherwise cannot be read. ConfigError: if the file's path is incorrect or otherwise cannot be read.
Returns: Returns:
A list of jinja2 templates. A list of jinja2 templates.
""" """
templates = []
search_directories = [self.default_template_dir] search_directories = [self.default_template_dir]
# The loader will first look in the custom template directory (if specified) for the # The loader will first look in the custom template directory (if specified) for the
@ -249,7 +263,7 @@ class Config:
search_directories.insert(0, custom_template_directory) search_directories.insert(0, custom_template_directory)
loader = jinja2.FileSystemLoader(search_directories) loader = jinja2.FileSystemLoader(search_directories)
env = jinja2.Environment(loader=loader, autoescape=autoescape) env = jinja2.Environment(loader=loader, autoescape=jinja2.select_autoescape(),)
# Update the environment with our custom filters # Update the environment with our custom filters
env.filters.update( env.filters.update(
@ -259,12 +273,8 @@ class Config:
} }
) )
for filename in filenames: # Load the templates
# Load the template return [env.get_template(filename) for filename in filenames]
template = env.get_template(filename)
templates.append(template)
return templates
def _format_ts_filter(value: int, format: str): def _format_ts_filter(value: int, format: str):

View file

@ -28,9 +28,7 @@ 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( self.recaptcha_template = self.read_template("recaptcha.html")
["recaptcha.html"], autoescape=True
)[0]
def generate_config_section(self, **kwargs): def generate_config_section(self, **kwargs):
return """\ return """\

View file

@ -89,7 +89,7 @@ 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] self.terms_template = self.read_template("terms.html")
if consent_config is None: if consent_config is None:
return return

View file

@ -176,9 +176,7 @@ class RegistrationConfig(Config):
self.session_lifetime = session_lifetime self.session_lifetime = session_lifetime
# The success template used during fallback auth. # The success template used during fallback auth.
self.fallback_success_template = self.read_templates( self.fallback_success_template = self.read_template("auth_success.html")
["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:

View file

@ -668,6 +668,15 @@ class Mailer:
def safe_markup(raw_html: str) -> jinja2.Markup: def safe_markup(raw_html: str) -> jinja2.Markup:
"""
Sanitise a raw HTML string to a set of allowed tags and attributes, and linkify any bare URLs.
Args
raw_html: Unsafe HTML.
Returns:
A Markup object ready to safely use in a Jinja template.
"""
return jinja2.Markup( return jinja2.Markup(
bleach.linkify( bleach.linkify(
bleach.clean( bleach.clean(
@ -684,8 +693,13 @@ def safe_markup(raw_html: str) -> jinja2.Markup:
def safe_text(raw_text: str) -> jinja2.Markup: def safe_text(raw_text: str) -> jinja2.Markup:
""" """
Process text: treat it as HTML but escape any tags (ie. just escape the Sanitise text (escape any HTML tags), and then linkify any bare URLs.
HTML) then linkify it.
Args
raw_text: Unsafe text which might include HTML markup.
Returns:
A Markup object ready to safely use in a Jinja template.
""" """
return jinja2.Markup( return jinja2.Markup(
bleach.linkify(bleach.clean(raw_text, tags=[], attributes={}, strip=False)) bleach.linkify(bleach.clean(raw_text, tags=[], attributes={}, strip=False))

View file

@ -5,7 +5,7 @@
<body> <body>
<div> <div>
<p> <p>
We were unable to validate your <tt>{{server_name | e}}</tt> account via We were unable to validate your <tt>{{ server_name }}</tt> account via
single-sign-on (SSO), because the SSO Identity Provider returned single-sign-on (SSO), because the SSO Identity Provider returned
different details than when you logged in. different details than when you logged in.
</p> </p>

View file

@ -5,8 +5,8 @@
<body> <body>
<div> <div>
<p> <p>
A client is trying to {{ description | e }}. To confirm this action, A client is trying to {{ description }}. To confirm this action,
<a href="{{ redirect_url | e }}">re-authenticate with single sign-on</a>. <a href="{{ redirect_url }}">re-authenticate with single sign-on</a>.
If you did not expect this, your account may be compromised! If you did not expect this, your account may be compromised!
</p> </p>
</div> </div>

View file

@ -12,7 +12,7 @@
<p> <p>
There was an error during authentication: There was an error during authentication:
</p> </p>
<div id="errormsg" style="margin:20px 80px">{{ error_description | e }}</div> <div id="errormsg" style="margin:20px 80px">{{ error_description }}</div>
<p> <p>
If you are seeing this page after clicking a link sent to you via email, make If you are seeing this page after clicking a link sent to you via email, make
sure you only click the confirmation link once, and that you open the sure you only click the confirmation link once, and that you open the

View file

@ -3,22 +3,22 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="/_matrix/static/client/login/style.css"> <link rel="stylesheet" href="/_matrix/static/client/login/style.css">
<title>{{server_name | e}} Login</title> <title>{{ server_name }} Login</title>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<h1 id="title">{{server_name | e}} Login</h1> <h1 id="title">{{ server_name }} Login</h1>
<div class="login_flow"> <div class="login_flow">
<p>Choose one of the following identity providers:</p> <p>Choose one of the following identity providers:</p>
<form> <form>
<input type="hidden" name="redirectUrl" value="{{redirect_url | e}}"> <input type="hidden" name="redirectUrl" value="{{ redirect_url }}">
<ul class="radiobuttons"> <ul class="radiobuttons">
{% for p in providers %} {% for p in providers %}
<li> <li>
<input type="radio" name="idp" id="prov{{loop.index}}" value="{{p.idp_id}}"> <input type="radio" name="idp" id="prov{{ loop.index }}" value="{{ p.idp_id }}">
<label for="prov{{loop.index}}">{{p.idp_name | e}}</label> <label for="prov{{ loop.index }}">{{ p.idp_name }}</label>
{% if p.idp_icon %} {% if p.idp_icon %}
<img src="{{p.idp_icon | mxc_to_http(32, 32)}}"/> <img src="{{ p.idp_icon | mxc_to_http(32, 32) }}"/>
{% endif %} {% endif %}
</li> </li>
{% endfor %} {% endfor %}

View file

@ -5,10 +5,10 @@
<title>SSO redirect confirmation</title> <title>SSO redirect confirmation</title>
</head> </head>
<body> <body>
<p>The application at <span style="font-weight:bold">{{ display_url | e }}</span> is requesting full access to your <span style="font-weight:bold">{{ server_name }}</span> Matrix account.</p> <p>The application at <span style="font-weight:bold">{{ display_url }}</span> is requesting full access to your <span style="font-weight:bold">{{ server_name }}</span> Matrix account.</p>
<p>If you don't recognise this address, you should ignore this and close this tab.</p> <p>If you don't recognise this address, you should ignore this and close this tab.</p>
<p> <p>
<a href="{{ redirect_url | e }}">I trust this address</a> <a href="{{ redirect_url }}">I trust this address</a>
</p> </p>
</body> </body>
</html> </html>