Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Use the default templates when a custom template file cannot be found #8037

Merged
merged 41 commits into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3d4f917
Remove conditional requirement for already required Jinja2
anoadragon453 Aug 4, 2020
0046454
Create a generic function to read jinja2 templates
anoadragon453 Aug 5, 2020
4ace21a
Switch saml2_config to use read_templates
anoadragon453 Aug 5, 2020
e3c75b1
Change create_mxc_to_http_filter to public function
anoadragon453 Aug 5, 2020
dd81441
Update synapse/push/pusher.py to use read_templates
anoadragon453 Aug 6, 2020
15f5aa0
Convert synapse/handlers/auth.py to use read_templates
anoadragon453 Aug 6, 2020
6f9e4b0
Convert synapse/handlers/account_validity.py to use read_templates
anoadragon453 Aug 6, 2020
e1ba6be
Convert synapse/handlers/oidc_handler.py to use read_templates
anoadragon453 Aug 6, 2020
44dc4aa
Convert synapse/rest/client/v2_alpha/account.py to use read_templates
anoadragon453 Aug 6, 2020
c8f105b
Convert synapse/rest/client/v2_alpha/register.py to use read_templates
anoadragon453 Aug 6, 2020
e8799e8
Remove load_jinja2_templates method
anoadragon453 Aug 6, 2020
2d442bf
Make template content variable names consistent
anoadragon453 Aug 6, 2020
7034971
Add a test
anoadragon453 Aug 6, 2020
00f4f63
Changelog
anoadragon453 Aug 6, 2020
2e7de8d
Merge branch 'develop' of github.com:matrix-org/synapse into anoa/def…
anoadragon453 Aug 14, 2020
7b674bf
sample config
anoadragon453 Aug 14, 2020
57c64c4
Simplify template loading code
anoadragon453 Aug 14, 2020
93555f4
Fix jinja environment filter update code
anoadragon453 Aug 14, 2020
ab3f058
Remove extra comma
anoadragon453 Aug 14, 2020
bcb04ad
Autoescape always enabled
anoadragon453 Aug 14, 2020
c77608c
Filters are not template-specific
anoadragon453 Aug 14, 2020
bf79f9e
Add docstring and improve create_mxc_to_http_filter
anoadragon453 Aug 14, 2020
49dbd26
Use tempfile.TemporaryDirectory in tests to clean up tmpdir
anoadragon453 Aug 14, 2020
1dfca15
misc -> feature
anoadragon453 Aug 14, 2020
45d6e4c
Add test for a custom template directory
anoadragon453 Aug 14, 2020
de41b66
Move read_templates to Config class
anoadragon453 Aug 14, 2020
e7ca492
Raise ConfigError if custom template directory does not exist
anoadragon453 Aug 14, 2020
c81cdb6
Add test for ConfigError being raised on invalid custom template dir
anoadragon453 Aug 14, 2020
87fea4c
Switch all read_templates calls from handlers to synapse.config
anoadragon453 Aug 17, 2020
7660398
Add typing to filter methods
anoadragon453 Aug 17, 2020
d9d8d72
Use custom filters on all templates
anoadragon453 Aug 17, 2020
405db9a
Switch filter methods to be private
anoadragon453 Aug 17, 2020
93b71d8
Only abspath the template_dir if it's set
anoadragon453 Aug 17, 2020
a1d7d9c
Fix a couple missed read_templates in account.py
anoadragon453 Aug 17, 2020
94300cc
Switch sso read_file calls to read_templates
anoadragon453 Aug 17, 2020
f08b610
Move some sso read_templates calls to synapse.config
anoadragon453 Aug 17, 2020
7b943e6
Remove stale comment part
anoadragon453 Aug 17, 2020
5806a08
Apply self.config suggestions
anoadragon453 Aug 17, 2020
c654916
Remove unnecessary import
anoadragon453 Aug 17, 2020
d35c049
Some more hs.config -> self.config
anoadragon453 Aug 17, 2020
116a895
Render sso deactived/success templates
anoadragon453 Aug 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8037.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use the default template file when its equivalent is not found in a custom template directory.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 1 addition & 3 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2002,9 +2002,7 @@ email:
# Directory in which Synapse will try to find the template files below.
# If not set, default templates from within the Synapse package will be used.
#
# DO NOT UNCOMMENT THIS SETTING unless you want to customise the templates.
# If you *do* uncomment it, you will need to make sure that all the templates
# below are in the directory.
# Do not uncomment this setting unless you want to customise the templates.
#
# Synapse will look for the following templates in this directory:
#
Expand Down
155 changes: 88 additions & 67 deletions synapse/config/emailconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
import email.utils
import os
from enum import Enum
from typing import Optional
from typing import Callable, Dict, List, Optional

import attr
import jinja2
import pkg_resources

from ._base import Config, ConfigError
Expand Down Expand Up @@ -98,14 +99,19 @@ def read_config(self, config, **kwargs):
if parsed[1] == "":
raise RuntimeError("Invalid notif_from address")

template_dir = email_config.get("template_dir")
# we need an absolute path, because we change directory after starting (and
# Get the path to the default Synapse template directory
self.default_template_dir = pkg_resources.resource_filename(
"synapse", "res/templates"
)

# A user-configurable template directory. Templates will first be looked for here,
# and if not found, will be taken from the default template directory instead.
template_dir = email_config.get("template_dir") or self.default_template_dir
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

# We need an absolute path, because we change directory after starting (and
# we don't yet know what auxiliary templates like mail.css we will need).
# (Note that loading as package_resources with jinja.PackageLoader doesn't
# work for the same reason.)
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
if not template_dir:
template_dir = pkg_resources.resource_filename("synapse", "res/templates")

self.email_template_dir = os.path.abspath(template_dir)

self.email_enable_notifs = email_config.get("enable_notifs", False)
Expand Down Expand Up @@ -166,19 +172,6 @@ def read_config(self, config, **kwargs):
email_config.get("validation_token_lifetime", "1h")
)

if (
self.email_enable_notifs
or account_validity_renewal_enabled
or self.threepid_behaviour_email == ThreepidBehaviour.LOCAL
):
# make sure we can import the required deps
import bleach
import jinja2

# prevent unused warnings
jinja2
bleach

if self.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
missing = []
if not self.email_notif_from:
Expand Down Expand Up @@ -214,7 +207,6 @@ def read_config(self, config, **kwargs):
self.email_add_threepid_template_text = email_config.get(
"add_threepid_template_text", "add_threepid.txt"
)

self.email_password_reset_template_failure_html = email_config.get(
"password_reset_template_failure_html", "password_reset_failure.html"
)
Expand All @@ -237,43 +229,28 @@ def read_config(self, config, **kwargs):
"add_threepid_template_success_html", "add_threepid_success.html"
)

# Check templates exist
for f in [
self.email_password_reset_template_html,
self.email_password_reset_template_text,
self.email_registration_template_html,
self.email_registration_template_text,
self.email_add_threepid_template_html,
self.email_add_threepid_template_text,
self.email_password_reset_template_failure_html,
self.email_registration_template_failure_html,
self.email_add_threepid_template_failure_html,
email_password_reset_template_success_html,
email_registration_template_success_html,
email_add_threepid_template_success_html,
]:
p = os.path.join(self.email_template_dir, f)
if not os.path.isfile(p):
raise ConfigError("Unable to find template file %s" % (p,))

# Retrieve content of web templates
filepath = os.path.join(
self.email_template_dir, email_password_reset_template_success_html
)
self.email_password_reset_template_success_html = self.read_file(
filepath, "email.password_reset_template_success_html"
# Read and render web templates
(
password_reset_template_success_html_template,
registration_template_success_html_template,
add_threepid_template_success_html_template,
) = self.read_templates(
[
email_password_reset_template_success_html,
email_registration_template_success_html,
email_add_threepid_template_success_html,
],
self.email_template_dir,
)
filepath = os.path.join(
self.email_template_dir, email_registration_template_success_html
)
self.email_registration_template_success_html_content = self.read_file(
filepath, "email.registration_template_success_html"

self.email_password_reset_template_success_html_content = (
password_reset_template_success_html_template.render()
)
filepath = os.path.join(
self.email_template_dir, email_add_threepid_template_success_html
self.email_registration_template_success_html_content = (
registration_template_success_html_template.render()
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
)
self.email_add_threepid_template_success_html_content = self.read_file(
filepath, "email.add_threepid_template_success_html"
self.email_add_threepid_template_success_html_content = (
add_threepid_template_success_html_template.render()
)

if self.email_enable_notifs:
Expand All @@ -297,11 +274,6 @@ def read_config(self, config, **kwargs):
"notif_template_text", "notif_mail.txt"
)

for f in self.email_notif_template_text, self.email_notif_template_html:
p = os.path.join(self.email_template_dir, f)
if not os.path.isfile(p):
raise ConfigError("Unable to find email template file %s" % (p,))

self.email_notif_for_new_users = email_config.get(
"notif_for_new_users", True
)
Expand All @@ -317,11 +289,6 @@ def read_config(self, config, **kwargs):
"expiry_template_text", "notice_expiry.txt"
)

for f in self.email_expiry_template_text, self.email_expiry_template_html:
p = os.path.join(self.email_template_dir, f)
if not os.path.isfile(p):
raise ConfigError("Unable to find email template file %s" % (p,))

subjects_config = email_config.get("subjects", {})
subjects = {}

Expand Down Expand Up @@ -400,9 +367,7 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
# Directory in which Synapse will try to find the template files below.
# If not set, default templates from within the Synapse package will be used.
#
# DO NOT UNCOMMENT THIS SETTING unless you want to customise the templates.
# If you *do* uncomment it, you will need to make sure that all the templates
# below are in the directory.
# Do not uncomment this setting unless you want to customise the templates.
#
# Synapse will look for the following templates in this directory:
#
Expand Down Expand Up @@ -509,6 +474,62 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
% DEFAULT_SUBJECTS
)

def read_templates(
clokep marked this conversation as resolved.
Show resolved Hide resolved
self,
filenames: List[str],
custom_template_directory: Optional[str] = None,
autoescape: bool = True,
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
filters: Dict[str, Callable] = {},
) -> List[jinja2.Template]:
"""Load a list of template files from disk using the given variables and filters.

This function will attempt to load the given templates from the default Synapse
template directory. If `custom_template_directory` is supplied, that directory
is tried first.

Files read are treated as Jinja templates. These templates are not rendered yet.

Args:
filenames: A list of template filenames to read.

custom_template_directory: A directory to try to look for the templates
before using the default Synapse template directory instead.

autoescape: Whether to automatically escape template variables when the templates
are rendered.

filters: Custom functions which can modify the given template variables upon
render, if the template file specifies them.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

Raises:
ConfigError: if the file's path is incorrect or otherwise cannot be read.

Returns:
A list of jinja2 templates.
"""
templates = []
search_directories = [self.default_template_dir]

# The loader will first look in the custom template directory (if specified) for the
# given filename. If it doesn't find it, it will use the default template dir instead
if custom_template_directory:
# Search the custom template directory as well
search_directories.insert(0, custom_template_directory)

loader = jinja2.FileSystemLoader(search_directories)
env = jinja2.Environment(loader=loader, autoescape=autoescape)

for filename in filenames:
# Update the environment with the default filters plus any custom ones
env.filters = jinja2.filters.FILTERS.copy()
env.filters.update(filters)
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved

# Load the template
template = env.get_template(filename)
templates.append(template)

return templates


class ThreepidBehaviour(Enum):
"""
Expand Down
14 changes: 3 additions & 11 deletions synapse/config/saml2_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
from typing import Any, List

import attr
import jinja2
import pkg_resources

from synapse.python_dependencies import DependencyException, check_requirements
from synapse.util.module_loader import load_module, load_python_module
Expand Down Expand Up @@ -171,15 +169,9 @@ def read_config(self, config, **kwargs):
saml2_config.get("saml_session_lifetime", "15m")
)

template_dir = saml2_config.get("template_dir")
if not template_dir:
template_dir = pkg_resources.resource_filename("synapse", "res/templates",)

loader = jinja2.FileSystemLoader(template_dir)
# enable auto-escape here, to having to remember to escape manually in the
# template
env = jinja2.Environment(loader=loader, autoescape=True)
self.saml2_error_html_template = env.get_template("saml_error.html")
self.saml2_error_html_template = self.read_templates(
["saml_error.html"], saml2_config.get("template_dir")
)

def _default_saml_config_dict(
self, required_attributes: set, optional_attributes: set
Expand Down
18 changes: 7 additions & 11 deletions synapse/handlers/account_validity.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,10 @@
from synapse.api.errors import StoreError
from synapse.logging.context import make_deferred_yieldable
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.push.mailer import create_mxc_to_http_filter, format_ts_filter
from synapse.types import UserID
from synapse.util import stringutils

try:
from synapse.push.mailer import load_jinja2_templates
except ImportError:
load_jinja2_templates = None

logger = logging.getLogger(__name__)


Expand All @@ -47,7 +43,6 @@ def __init__(self, hs):
if (
self._account_validity.enabled
and self._account_validity.renew_by_email_enabled
and load_jinja2_templates
):
# Don't do email-specific configuration if renewal by email is disabled.
try:
Expand All @@ -65,15 +60,16 @@ def __init__(self, hs):

self._raw_from = email.utils.parseaddr(self._from_string)[1]

self._template_html, self._template_text = load_jinja2_templates(
self.config.email_template_dir,
self._template_html, self._template_text = hs.config.read_templates(
[
self.config.email_expiry_template_html,
self.config.email_expiry_template_text,
],
apply_format_ts_filter=True,
apply_mxc_to_http_filter=True,
public_baseurl=self.config.public_baseurl,
self.config.email_template_dir,
filters={
"format_ts": format_ts_filter,
"mxc_to_http": create_mxc_to_http_filter(hs.config.public_baseurl),
},
clokep marked this conversation as resolved.
Show resolved Hide resolved
)

# Check the renewal emails to send and send them every 30min.
Expand Down
9 changes: 4 additions & 5 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
from synapse.logging.context import defer_to_thread
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.module_api import ModuleApi
from synapse.push.mailer import load_jinja2_templates
from synapse.types import Requester, UserID
from synapse.util import stringutils as stringutils
from synapse.util.threepids import canonicalise_email
Expand Down Expand Up @@ -132,14 +131,14 @@ def __init__(self, hs):
# after the SSO completes and before redirecting them back to their client.
# It notifies the user they are about to give access to their matrix account
# to the client.
self._sso_redirect_confirm_template = load_jinja2_templates(
hs.config.sso_template_dir, ["sso_redirect_confirm.html"],
self._sso_redirect_confirm_template = hs.config.read_templates(
["sso_redirect_confirm.html"], hs.config.sso_template_dir
)[0]
clokep marked this conversation as resolved.
Show resolved Hide resolved
# The following template is shown during user interactive authentication
# in the fallback auth scenario. It notifies the user that they are
# authenticating for an operation to occur on their account.
self._sso_auth_confirm_template = load_jinja2_templates(
hs.config.sso_template_dir, ["sso_auth_confirm.html"],
self._sso_auth_confirm_template = hs.config.read_templates(
["sso_auth_confirm.html"], hs.config.sso_template_dir
)[0]
# The following template is shown after a successful user interactive
# authentication session. It tells the user they can close the window.
Expand Down
5 changes: 2 additions & 3 deletions synapse/handlers/oidc_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from synapse.http.server import respond_with_html
from synapse.http.site import SynapseRequest
from synapse.logging.context import make_deferred_yieldable
from synapse.push.mailer import load_jinja2_templates
from synapse.types import UserID, map_username_to_mxid_localpart

if TYPE_CHECKING:
Expand Down Expand Up @@ -123,8 +122,8 @@ def __init__(self, hs: "HomeServer"):
self._hostname = hs.hostname # type: str
self._server_name = hs.config.server_name # type: str
self._macaroon_secret_key = hs.config.macaroon_secret_key
self._error_template = load_jinja2_templates(
hs.config.sso_template_dir, ["sso_error.html"]
self._error_template = hs.config.read_templates(
["sso_error.html"], hs.config.sso_template_dir
)[0]

# identifier for the external_ids table
Expand Down
Loading