Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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.feature
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.
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
100 changes: 99 additions & 1 deletion synapse/config/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
import argparse
import errno
import os
import time
import urllib.parse
from collections import OrderedDict
from hashlib import sha256
from textwrap import dedent
from typing import Any, List, MutableMapping, Optional
from typing import Any, Callable, List, MutableMapping, Optional

import attr
import jinja2
import pkg_resources
import yaml


Expand Down Expand Up @@ -100,6 +104,11 @@ class Config(object):
def __init__(self, root_config=None):
self.root = root_config

# Get the path to the default Synapse template directory
self.default_template_dir = pkg_resources.resource_filename(
"synapse", "res/templates"
)

def __getattr__(self, item: str) -> Any:
"""
Try and fetch a configuration option that does not exist on this class.
Expand Down Expand Up @@ -184,6 +193,95 @@ def read_file(cls, file_path, config_name):
with open(file_path) as file_stream:
return file_stream.read()

def read_templates(
self, filenames: List[str], custom_template_directory: Optional[str] = None,
) -> List[jinja2.Template]:
"""Load a list of template files from disk using the given variables.

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.

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:
# Check that the given template directory exists
if not self.path_exists(custom_template_directory):
raise ConfigError(
"Configured template directory does not exist: %s"
% (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=True)

# Update the environment with our custom filters
env.filters.update(
{
"format_ts": _format_ts_filter,
"mxc_to_http": _create_mxc_to_http_filter(self.public_baseurl),
}
)

for filename in filenames:
# Load the template
template = env.get_template(filename)
templates.append(template)

return templates


def _format_ts_filter(value: int, format: str):
return time.strftime(format, time.localtime(value / 1000))


def _create_mxc_to_http_filter(public_baseurl: str) -> Callable:
"""Create and return a jinja2 filter that converts MXC urls to HTTP

Args:
public_baseurl: The public, accessible base URL of the homeserver
"""

def mxc_to_http_filter(value, width, height, resize_method="crop"):
if value[0:6] != "mxc://":
return ""

server_and_media_id = value[6:]
fragment = None
if "#" in server_and_media_id:
server_and_media_id, fragment = server_and_media_id.split("#", 1)
fragment = "#" + fragment

params = {"width": width, "height": height, "method": resize_method}
return "%s_matrix/media/v1/thumbnail/%s?%s%s" % (
public_baseurl,
server_and_media_id,
urllib.parse.urlencode(params),
fragment or "",
)

return mxc_to_http_filter


class RootConfig(object):
"""
Expand Down
145 changes: 67 additions & 78 deletions synapse/config/emailconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from typing import Optional

import attr
import pkg_resources

from ._base import Config, ConfigError

Expand Down Expand Up @@ -98,21 +97,18 @@ def read_config(self, config, **kwargs):
if parsed[1] == "":
raise RuntimeError("Invalid notif_from address")

# A user-configurable template directory
template_dir = email_config.get("template_dir")
# 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.)
if not template_dir:
template_dir = pkg_resources.resource_filename("synapse", "res/templates")

self.email_template_dir = os.path.abspath(template_dir)
if isinstance(template_dir, str):
# 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).
template_dir = os.path.abspath(template_dir)
elif template_dir is not None:
# If template_dir is something other than a str or None, warn the user
raise ConfigError("Config option email.template_dir must be type str")

self.email_enable_notifs = email_config.get("enable_notifs", False)

account_validity_config = config.get("account_validity") or {}
account_validity_renewal_enabled = account_validity_config.get("renew_at")

self.threepid_behaviour_email = (
# Have Synapse handle the email sending if account_threepid_delegates.email
# is not defined
Expand Down Expand Up @@ -166,19 +162,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 All @@ -196,49 +179,49 @@ def read_config(self, config, **kwargs):

# These email templates have placeholders in them, and thus must be
# parsed using a templating engine during a request
self.email_password_reset_template_html = email_config.get(
password_reset_template_html = email_config.get(
"password_reset_template_html", "password_reset.html"
)
self.email_password_reset_template_text = email_config.get(
password_reset_template_text = email_config.get(
"password_reset_template_text", "password_reset.txt"
)
self.email_registration_template_html = email_config.get(
registration_template_html = email_config.get(
"registration_template_html", "registration.html"
)
self.email_registration_template_text = email_config.get(
registration_template_text = email_config.get(
"registration_template_text", "registration.txt"
)
self.email_add_threepid_template_html = email_config.get(
add_threepid_template_html = email_config.get(
"add_threepid_template_html", "add_threepid.html"
)
self.email_add_threepid_template_text = email_config.get(
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 = email_config.get(
"password_reset_template_failure_html", "password_reset_failure.html"
)
self.email_registration_template_failure_html = email_config.get(
registration_template_failure_html = email_config.get(
"registration_template_failure_html", "registration_failure.html"
)
self.email_add_threepid_template_failure_html = email_config.get(
add_threepid_template_failure_html = email_config.get(
"add_threepid_template_failure_html", "add_threepid_failure.html"
)

# These templates do not support any placeholder variables, so we
# will read them from disk once during setup
email_password_reset_template_success_html = email_config.get(
password_reset_template_success_html = email_config.get(
"password_reset_template_success_html", "password_reset_success.html"
)
email_registration_template_success_html = email_config.get(
registration_template_success_html = email_config.get(
"registration_template_success_html", "registration_success.html"
)
email_add_threepid_template_success_html = email_config.get(
add_threepid_template_success_html = email_config.get(
"add_threepid_template_success_html", "add_threepid_success.html"
)

# Check templates exist
for f in [
# Read all templates from disk
(
self.email_password_reset_template_html,
self.email_password_reset_template_text,
self.email_registration_template_html,
Expand All @@ -248,32 +231,36 @@ def read_config(self, config, **kwargs):
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
password_reset_template_success_html_template,
registration_template_success_html_template,
add_threepid_template_success_html_template,
) = self.read_templates(
[
password_reset_template_html,
password_reset_template_text,
registration_template_html,
registration_template_text,
add_threepid_template_html,
add_threepid_template_text,
password_reset_template_failure_html,
registration_template_failure_html,
add_threepid_template_failure_html,
password_reset_template_success_html,
registration_template_success_html,
add_threepid_template_success_html,
],
template_dir,
)
self.email_password_reset_template_success_html = self.read_file(
filepath, "email.password_reset_template_success_html"
)
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"

# Render templates that do not contain any placeholders
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()
)
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 @@ -290,17 +277,19 @@ def read_config(self, config, **kwargs):
% (", ".join(missing),)
)

self.email_notif_template_html = email_config.get(
notif_template_html = email_config.get(
"notif_template_html", "notif_mail.html"
)
self.email_notif_template_text = email_config.get(
notif_template_text = email_config.get(
"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_template_html,
self.email_notif_template_text,
) = self.read_templates(
[notif_template_html, notif_template_text], template_dir,
)

self.email_notif_for_new_users = email_config.get(
"notif_for_new_users", True
Expand All @@ -309,18 +298,20 @@ def read_config(self, config, **kwargs):
"client_base_url", email_config.get("riot_base_url", None)
)

if account_validity_renewal_enabled:
self.email_expiry_template_html = email_config.get(
if self.account_validity.renew_by_email_enabled:
expiry_template_html = email_config.get(
"expiry_template_html", "notice_expiry.html"
)
self.email_expiry_template_text = email_config.get(
expiry_template_text = email_config.get(
"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,))
(
self.account_validity_template_html,
self.account_validity_template_text,
) = self.read_templates(
[expiry_template_html, expiry_template_text], template_dir,
)

subjects_config = email_config.get("subjects", {})
subjects = {}
Expand Down Expand Up @@ -400,9 +391,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
Loading