Use the default templates when a custom template file cannot be found (#8037)
Fixes https://github.com/matrix-org/synapse/issues/6583
This commit is contained in:
parent
8390e00c7f
commit
e04e465b4d
|
@ -0,0 +1 @@
|
||||||
|
Use the default template file when its equivalent is not found in a custom template directory.
|
|
@ -2002,9 +2002,7 @@ email:
|
||||||
# Directory in which Synapse will try to find the template files below.
|
# 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.
|
# 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.
|
# 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.
|
|
||||||
#
|
#
|
||||||
# Synapse will look for the following templates in this directory:
|
# Synapse will look for the following templates in this directory:
|
||||||
#
|
#
|
||||||
|
|
|
@ -18,12 +18,16 @@
|
||||||
import argparse
|
import argparse
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
import urllib.parse
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Any, List, MutableMapping, Optional
|
from typing import Any, Callable, List, MutableMapping, Optional
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
import jinja2
|
||||||
|
import pkg_resources
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,6 +104,11 @@ class Config(object):
|
||||||
def __init__(self, root_config=None):
|
def __init__(self, root_config=None):
|
||||||
self.root = root_config
|
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:
|
def __getattr__(self, item: str) -> Any:
|
||||||
"""
|
"""
|
||||||
Try and fetch a configuration option that does not exist on this class.
|
Try and fetch a configuration option that does not exist on this class.
|
||||||
|
@ -184,6 +193,95 @@ class Config(object):
|
||||||
with open(file_path) as file_stream:
|
with open(file_path) as file_stream:
|
||||||
return file_stream.read()
|
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):
|
class RootConfig(object):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -23,7 +23,6 @@ from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from ._base import Config, ConfigError
|
from ._base import Config, ConfigError
|
||||||
|
|
||||||
|
@ -98,21 +97,18 @@ class EmailConfig(Config):
|
||||||
if parsed[1] == "":
|
if parsed[1] == "":
|
||||||
raise RuntimeError("Invalid notif_from address")
|
raise RuntimeError("Invalid notif_from address")
|
||||||
|
|
||||||
|
# A user-configurable template directory
|
||||||
template_dir = email_config.get("template_dir")
|
template_dir = email_config.get("template_dir")
|
||||||
# we need an absolute path, because we change directory after starting (and
|
if isinstance(template_dir, str):
|
||||||
# we don't yet know what auxiliary templates like mail.css we will need).
|
# We need an absolute path, because we change directory after starting (and
|
||||||
# (Note that loading as package_resources with jinja.PackageLoader doesn't
|
# we don't yet know what auxiliary templates like mail.css we will need).
|
||||||
# work for the same reason.)
|
template_dir = os.path.abspath(template_dir)
|
||||||
if not template_dir:
|
elif template_dir is not None:
|
||||||
template_dir = pkg_resources.resource_filename("synapse", "res/templates")
|
# 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_template_dir = os.path.abspath(template_dir)
|
|
||||||
|
|
||||||
self.email_enable_notifs = email_config.get("enable_notifs", False)
|
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 = (
|
self.threepid_behaviour_email = (
|
||||||
# Have Synapse handle the email sending if account_threepid_delegates.email
|
# Have Synapse handle the email sending if account_threepid_delegates.email
|
||||||
# is not defined
|
# is not defined
|
||||||
|
@ -166,19 +162,6 @@ class EmailConfig(Config):
|
||||||
email_config.get("validation_token_lifetime", "1h")
|
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:
|
if self.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
missing = []
|
missing = []
|
||||||
if not self.email_notif_from:
|
if not self.email_notif_from:
|
||||||
|
@ -196,49 +179,49 @@ class EmailConfig(Config):
|
||||||
|
|
||||||
# These email templates have placeholders in them, and thus must be
|
# These email templates have placeholders in them, and thus must be
|
||||||
# parsed using a templating engine during a request
|
# 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"
|
"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"
|
"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"
|
"registration_template_html", "registration.html"
|
||||||
)
|
)
|
||||||
self.email_registration_template_text = email_config.get(
|
registration_template_text = email_config.get(
|
||||||
"registration_template_text", "registration.txt"
|
"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"
|
"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"
|
"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"
|
"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"
|
"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"
|
"add_threepid_template_failure_html", "add_threepid_failure.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
# These templates do not support any placeholder variables, so we
|
# These templates do not support any placeholder variables, so we
|
||||||
# will read them from disk once during setup
|
# 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"
|
"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"
|
"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"
|
"add_threepid_template_success_html", "add_threepid_success.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check templates exist
|
# Read all templates from disk
|
||||||
for f in [
|
(
|
||||||
self.email_password_reset_template_html,
|
self.email_password_reset_template_html,
|
||||||
self.email_password_reset_template_text,
|
self.email_password_reset_template_text,
|
||||||
self.email_registration_template_html,
|
self.email_registration_template_html,
|
||||||
|
@ -248,32 +231,36 @@ class EmailConfig(Config):
|
||||||
self.email_password_reset_template_failure_html,
|
self.email_password_reset_template_failure_html,
|
||||||
self.email_registration_template_failure_html,
|
self.email_registration_template_failure_html,
|
||||||
self.email_add_threepid_template_failure_html,
|
self.email_add_threepid_template_failure_html,
|
||||||
email_password_reset_template_success_html,
|
password_reset_template_success_html_template,
|
||||||
email_registration_template_success_html,
|
registration_template_success_html_template,
|
||||||
email_add_threepid_template_success_html,
|
add_threepid_template_success_html_template,
|
||||||
]:
|
) = self.read_templates(
|
||||||
p = os.path.join(self.email_template_dir, f)
|
[
|
||||||
if not os.path.isfile(p):
|
password_reset_template_html,
|
||||||
raise ConfigError("Unable to find template file %s" % (p,))
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
# Retrieve content of web templates
|
# Render templates that do not contain any placeholders
|
||||||
filepath = os.path.join(
|
self.email_password_reset_template_success_html_content = (
|
||||||
self.email_template_dir, email_password_reset_template_success_html
|
password_reset_template_success_html_template.render()
|
||||||
)
|
)
|
||||||
self.email_password_reset_template_success_html = self.read_file(
|
self.email_registration_template_success_html_content = (
|
||||||
filepath, "email.password_reset_template_success_html"
|
registration_template_success_html_template.render()
|
||||||
)
|
)
|
||||||
filepath = os.path.join(
|
self.email_add_threepid_template_success_html_content = (
|
||||||
self.email_template_dir, email_registration_template_success_html
|
add_threepid_template_success_html_template.render()
|
||||||
)
|
|
||||||
self.email_registration_template_success_html_content = self.read_file(
|
|
||||||
filepath, "email.registration_template_success_html"
|
|
||||||
)
|
|
||||||
filepath = os.path.join(
|
|
||||||
self.email_template_dir, email_add_threepid_template_success_html
|
|
||||||
)
|
|
||||||
self.email_add_threepid_template_success_html_content = self.read_file(
|
|
||||||
filepath, "email.add_threepid_template_success_html"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.email_enable_notifs:
|
if self.email_enable_notifs:
|
||||||
|
@ -290,17 +277,19 @@ class EmailConfig(Config):
|
||||||
% (", ".join(missing),)
|
% (", ".join(missing),)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.email_notif_template_html = email_config.get(
|
notif_template_html = email_config.get(
|
||||||
"notif_template_html", "notif_mail.html"
|
"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"
|
"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)
|
self.email_notif_template_html,
|
||||||
if not os.path.isfile(p):
|
self.email_notif_template_text,
|
||||||
raise ConfigError("Unable to find email template file %s" % (p,))
|
) = self.read_templates(
|
||||||
|
[notif_template_html, notif_template_text], template_dir,
|
||||||
|
)
|
||||||
|
|
||||||
self.email_notif_for_new_users = email_config.get(
|
self.email_notif_for_new_users = email_config.get(
|
||||||
"notif_for_new_users", True
|
"notif_for_new_users", True
|
||||||
|
@ -309,18 +298,20 @@ class EmailConfig(Config):
|
||||||
"client_base_url", email_config.get("riot_base_url", None)
|
"client_base_url", email_config.get("riot_base_url", None)
|
||||||
)
|
)
|
||||||
|
|
||||||
if account_validity_renewal_enabled:
|
if self.account_validity.renew_by_email_enabled:
|
||||||
self.email_expiry_template_html = email_config.get(
|
expiry_template_html = email_config.get(
|
||||||
"expiry_template_html", "notice_expiry.html"
|
"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"
|
"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)
|
self.account_validity_template_html,
|
||||||
if not os.path.isfile(p):
|
self.account_validity_template_text,
|
||||||
raise ConfigError("Unable to find email template file %s" % (p,))
|
) = self.read_templates(
|
||||||
|
[expiry_template_html, expiry_template_text], template_dir,
|
||||||
|
)
|
||||||
|
|
||||||
subjects_config = email_config.get("subjects", {})
|
subjects_config = email_config.get("subjects", {})
|
||||||
subjects = {}
|
subjects = {}
|
||||||
|
@ -400,9 +391,7 @@ class EmailConfig(Config):
|
||||||
# Directory in which Synapse will try to find the template files below.
|
# 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.
|
# 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.
|
# 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.
|
|
||||||
#
|
#
|
||||||
# Synapse will look for the following templates in this directory:
|
# Synapse will look for the following templates in this directory:
|
||||||
#
|
#
|
||||||
|
|
|
@ -18,8 +18,6 @@ import logging
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import jinja2
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from synapse.python_dependencies import DependencyException, check_requirements
|
from synapse.python_dependencies import DependencyException, check_requirements
|
||||||
from synapse.util.module_loader import load_module, load_python_module
|
from synapse.util.module_loader import load_module, load_python_module
|
||||||
|
@ -171,15 +169,9 @@ class SAML2Config(Config):
|
||||||
saml2_config.get("saml_session_lifetime", "15m")
|
saml2_config.get("saml_session_lifetime", "15m")
|
||||||
)
|
)
|
||||||
|
|
||||||
template_dir = saml2_config.get("template_dir")
|
self.saml2_error_html_template = self.read_templates(
|
||||||
if not template_dir:
|
["saml_error.html"], saml2_config.get("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")
|
|
||||||
|
|
||||||
def _default_saml_config_dict(
|
def _default_saml_config_dict(
|
||||||
self, required_attributes: set, optional_attributes: set
|
self, required_attributes: set, optional_attributes: set
|
||||||
|
|
|
@ -12,11 +12,8 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import os
|
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from ._base import Config
|
from ._base import Config
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,22 +26,32 @@ class SSOConfig(Config):
|
||||||
def read_config(self, config, **kwargs):
|
def read_config(self, config, **kwargs):
|
||||||
sso_config = config.get("sso") or {} # type: Dict[str, Any]
|
sso_config = config.get("sso") or {} # type: Dict[str, Any]
|
||||||
|
|
||||||
# Pick a template directory in order of:
|
# The sso-specific template_dir
|
||||||
# * The sso-specific template_dir
|
|
||||||
# * /path/to/synapse/install/res/templates
|
|
||||||
template_dir = sso_config.get("template_dir")
|
template_dir = sso_config.get("template_dir")
|
||||||
if not template_dir:
|
|
||||||
template_dir = pkg_resources.resource_filename("synapse", "res/templates",)
|
|
||||||
|
|
||||||
self.sso_template_dir = template_dir
|
# Read templates from disk
|
||||||
self.sso_account_deactivated_template = self.read_file(
|
(
|
||||||
os.path.join(self.sso_template_dir, "sso_account_deactivated.html"),
|
self.sso_redirect_confirm_template,
|
||||||
"sso_account_deactivated_template",
|
self.sso_auth_confirm_template,
|
||||||
|
self.sso_error_template,
|
||||||
|
sso_account_deactivated_template,
|
||||||
|
sso_auth_success_template,
|
||||||
|
) = self.read_templates(
|
||||||
|
[
|
||||||
|
"sso_redirect_confirm.html",
|
||||||
|
"sso_auth_confirm.html",
|
||||||
|
"sso_error.html",
|
||||||
|
"sso_account_deactivated.html",
|
||||||
|
"sso_auth_success.html",
|
||||||
|
],
|
||||||
|
template_dir,
|
||||||
)
|
)
|
||||||
self.sso_auth_success_template = self.read_file(
|
|
||||||
os.path.join(self.sso_template_dir, "sso_auth_success.html"),
|
# These templates have no placeholders, so render them here
|
||||||
"sso_auth_success_template",
|
self.sso_account_deactivated_template = (
|
||||||
|
sso_account_deactivated_template.render()
|
||||||
)
|
)
|
||||||
|
self.sso_auth_success_template = sso_auth_success_template.render()
|
||||||
|
|
||||||
self.sso_client_whitelist = sso_config.get("client_whitelist") or []
|
self.sso_client_whitelist = sso_config.get("client_whitelist") or []
|
||||||
|
|
||||||
|
|
|
@ -26,11 +26,6 @@ from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
from synapse.types import UserID
|
from synapse.types import UserID
|
||||||
from synapse.util import stringutils
|
from synapse.util import stringutils
|
||||||
|
|
||||||
try:
|
|
||||||
from synapse.push.mailer import load_jinja2_templates
|
|
||||||
except ImportError:
|
|
||||||
load_jinja2_templates = None
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,9 +42,11 @@ class AccountValidityHandler(object):
|
||||||
if (
|
if (
|
||||||
self._account_validity.enabled
|
self._account_validity.enabled
|
||||||
and self._account_validity.renew_by_email_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.
|
# Don't do email-specific configuration if renewal by email is disabled.
|
||||||
|
self._template_html = self.config.account_validity_template_html
|
||||||
|
self._template_text = self.config.account_validity_template_text
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app_name = self.hs.config.email_app_name
|
app_name = self.hs.config.email_app_name
|
||||||
|
|
||||||
|
@ -65,17 +62,6 @@ class AccountValidityHandler(object):
|
||||||
|
|
||||||
self._raw_from = email.utils.parseaddr(self._from_string)[1]
|
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.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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check the renewal emails to send and send them every 30min.
|
# Check the renewal emails to send and send them every 30min.
|
||||||
def send_emails():
|
def send_emails():
|
||||||
# run as a background process to make sure that the database transactions
|
# run as a background process to make sure that the database transactions
|
||||||
|
|
|
@ -42,7 +42,6 @@ from synapse.http.site import SynapseRequest
|
||||||
from synapse.logging.context import defer_to_thread
|
from synapse.logging.context import defer_to_thread
|
||||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||||
from synapse.module_api import ModuleApi
|
from synapse.module_api import ModuleApi
|
||||||
from synapse.push.mailer import load_jinja2_templates
|
|
||||||
from synapse.types import Requester, UserID
|
from synapse.types import Requester, UserID
|
||||||
from synapse.util import stringutils as stringutils
|
from synapse.util import stringutils as stringutils
|
||||||
from synapse.util.threepids import canonicalise_email
|
from synapse.util.threepids import canonicalise_email
|
||||||
|
@ -132,18 +131,17 @@ class AuthHandler(BaseHandler):
|
||||||
# after the SSO completes and before redirecting them back to their client.
|
# 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
|
# It notifies the user they are about to give access to their matrix account
|
||||||
# to the client.
|
# to the client.
|
||||||
self._sso_redirect_confirm_template = load_jinja2_templates(
|
self._sso_redirect_confirm_template = hs.config.sso_redirect_confirm_template
|
||||||
hs.config.sso_template_dir, ["sso_redirect_confirm.html"],
|
|
||||||
)[0]
|
|
||||||
# The following template is shown during user interactive authentication
|
# The following template is shown during user interactive authentication
|
||||||
# in the fallback auth scenario. It notifies the user that they are
|
# in the fallback auth scenario. It notifies the user that they are
|
||||||
# authenticating for an operation to occur on their account.
|
# authenticating for an operation to occur on their account.
|
||||||
self._sso_auth_confirm_template = load_jinja2_templates(
|
self._sso_auth_confirm_template = hs.config.sso_auth_confirm_template
|
||||||
hs.config.sso_template_dir, ["sso_auth_confirm.html"],
|
|
||||||
)[0]
|
|
||||||
# The following template is shown after a successful user interactive
|
# The following template is shown after a successful user interactive
|
||||||
# authentication session. It tells the user they can close the window.
|
# authentication session. It tells the user they can close the window.
|
||||||
self._sso_auth_success_template = hs.config.sso_auth_success_template
|
self._sso_auth_success_template = hs.config.sso_auth_success_template
|
||||||
|
|
||||||
# The following template is shown during the SSO authentication process if
|
# The following template is shown during the SSO authentication process if
|
||||||
# the account is deactivated.
|
# the account is deactivated.
|
||||||
self._sso_account_deactivated_template = (
|
self._sso_account_deactivated_template = (
|
||||||
|
|
|
@ -38,7 +38,6 @@ from synapse.config import ConfigError
|
||||||
from synapse.http.server import respond_with_html
|
from synapse.http.server import respond_with_html
|
||||||
from synapse.http.site import SynapseRequest
|
from synapse.http.site import SynapseRequest
|
||||||
from synapse.logging.context import make_deferred_yieldable
|
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
|
from synapse.types import UserID, map_username_to_mxid_localpart
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -123,9 +122,7 @@ class OidcHandler:
|
||||||
self._hostname = hs.hostname # type: str
|
self._hostname = hs.hostname # type: str
|
||||||
self._server_name = hs.config.server_name # type: str
|
self._server_name = hs.config.server_name # type: str
|
||||||
self._macaroon_secret_key = hs.config.macaroon_secret_key
|
self._macaroon_secret_key = hs.config.macaroon_secret_key
|
||||||
self._error_template = load_jinja2_templates(
|
self._error_template = hs.config.sso_error_template
|
||||||
hs.config.sso_template_dir, ["sso_error.html"]
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
# identifier for the external_ids table
|
# identifier for the external_ids table
|
||||||
self._auth_provider_id = "oidc"
|
self._auth_provider_id = "oidc"
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
import email.mime.multipart
|
import email.mime.multipart
|
||||||
import email.utils
|
import email.utils
|
||||||
import logging
|
import logging
|
||||||
import time
|
import urllib.parse
|
||||||
import urllib
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from typing import Iterable, List, TypeVar
|
from typing import Iterable, List, TypeVar
|
||||||
|
@ -640,72 +639,3 @@ def string_ordinal_total(s):
|
||||||
for c in s:
|
for c in s:
|
||||||
tot += ord(c)
|
tot += ord(c)
|
||||||
return tot
|
return tot
|
||||||
|
|
||||||
|
|
||||||
def format_ts_filter(value, format):
|
|
||||||
return time.strftime(format, time.localtime(value / 1000))
|
|
||||||
|
|
||||||
|
|
||||||
def load_jinja2_templates(
|
|
||||||
template_dir,
|
|
||||||
template_filenames,
|
|
||||||
apply_format_ts_filter=False,
|
|
||||||
apply_mxc_to_http_filter=False,
|
|
||||||
public_baseurl=None,
|
|
||||||
):
|
|
||||||
"""Loads and returns one or more jinja2 templates and applies optional filters
|
|
||||||
|
|
||||||
Args:
|
|
||||||
template_dir (str): The directory where templates are stored
|
|
||||||
template_filenames (list[str]): A list of template filenames
|
|
||||||
apply_format_ts_filter (bool): Whether to apply a template filter that formats
|
|
||||||
timestamps
|
|
||||||
apply_mxc_to_http_filter (bool): Whether to apply a template filter that converts
|
|
||||||
mxc urls to http urls
|
|
||||||
public_baseurl (str|None): The public baseurl of the server. Required for
|
|
||||||
apply_mxc_to_http_filter to be enabled
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A list of jinja2 templates corresponding to the given list of filenames,
|
|
||||||
with order preserved
|
|
||||||
"""
|
|
||||||
logger.info(
|
|
||||||
"loading email templates %s from '%s'", template_filenames, template_dir
|
|
||||||
)
|
|
||||||
loader = jinja2.FileSystemLoader(template_dir)
|
|
||||||
env = jinja2.Environment(loader=loader)
|
|
||||||
|
|
||||||
if apply_format_ts_filter:
|
|
||||||
env.filters["format_ts"] = format_ts_filter
|
|
||||||
|
|
||||||
if apply_mxc_to_http_filter and public_baseurl:
|
|
||||||
env.filters["mxc_to_http"] = _create_mxc_to_http_filter(public_baseurl)
|
|
||||||
|
|
||||||
templates = []
|
|
||||||
for template_filename in template_filenames:
|
|
||||||
template = env.get_template(template_filename)
|
|
||||||
templates.append(template)
|
|
||||||
|
|
||||||
return templates
|
|
||||||
|
|
||||||
|
|
||||||
def _create_mxc_to_http_filter(public_baseurl):
|
|
||||||
def mxc_to_http_filter(value, width, height, resize_method="crop"):
|
|
||||||
if value[0:6] != "mxc://":
|
|
||||||
return ""
|
|
||||||
|
|
||||||
serverAndMediaId = value[6:]
|
|
||||||
fragment = None
|
|
||||||
if "#" in serverAndMediaId:
|
|
||||||
(serverAndMediaId, fragment) = serverAndMediaId.split("#", 1)
|
|
||||||
fragment = "#" + fragment
|
|
||||||
|
|
||||||
params = {"width": width, "height": height, "method": resize_method}
|
|
||||||
return "%s_matrix/media/v1/thumbnail/%s?%s%s" % (
|
|
||||||
public_baseurl,
|
|
||||||
serverAndMediaId,
|
|
||||||
urllib.parse.urlencode(params),
|
|
||||||
fragment or "",
|
|
||||||
)
|
|
||||||
|
|
||||||
return mxc_to_http_filter
|
|
||||||
|
|
|
@ -15,22 +15,13 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from synapse.push.emailpusher import EmailPusher
|
||||||
|
from synapse.push.mailer import Mailer
|
||||||
|
|
||||||
from .httppusher import HttpPusher
|
from .httppusher import HttpPusher
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# We try importing this if we can (it will fail if we don't
|
|
||||||
# have the optional email dependencies installed). We don't
|
|
||||||
# yet have the config to know if we need the email pusher,
|
|
||||||
# but importing this after daemonizing seems to fail
|
|
||||||
# (even though a simple test of importing from a daemonized
|
|
||||||
# process works fine)
|
|
||||||
try:
|
|
||||||
from synapse.push.emailpusher import EmailPusher
|
|
||||||
from synapse.push.mailer import Mailer, load_jinja2_templates
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PusherFactory(object):
|
class PusherFactory(object):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
|
@ -43,16 +34,8 @@ class PusherFactory(object):
|
||||||
if hs.config.email_enable_notifs:
|
if hs.config.email_enable_notifs:
|
||||||
self.mailers = {} # app_name -> Mailer
|
self.mailers = {} # app_name -> Mailer
|
||||||
|
|
||||||
self.notif_template_html, self.notif_template_text = load_jinja2_templates(
|
self._notif_template_html = hs.config.email_notif_template_html
|
||||||
self.config.email_template_dir,
|
self._notif_template_text = hs.config.email_notif_template_text
|
||||||
[
|
|
||||||
self.config.email_notif_template_html,
|
|
||||||
self.config.email_notif_template_text,
|
|
||||||
],
|
|
||||||
apply_format_ts_filter=True,
|
|
||||||
apply_mxc_to_http_filter=True,
|
|
||||||
public_baseurl=self.config.public_baseurl,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.pusher_types["email"] = self._create_email_pusher
|
self.pusher_types["email"] = self._create_email_pusher
|
||||||
|
|
||||||
|
@ -73,8 +56,8 @@ class PusherFactory(object):
|
||||||
mailer = Mailer(
|
mailer = Mailer(
|
||||||
hs=self.hs,
|
hs=self.hs,
|
||||||
app_name=app_name,
|
app_name=app_name,
|
||||||
template_html=self.notif_template_html,
|
template_html=self._notif_template_html,
|
||||||
template_text=self.notif_template_text,
|
template_text=self._notif_template_text,
|
||||||
)
|
)
|
||||||
self.mailers[app_name] = mailer
|
self.mailers[app_name] = mailer
|
||||||
return EmailPusher(self.hs, pusherdict, mailer)
|
return EmailPusher(self.hs, pusherdict, mailer)
|
||||||
|
|
|
@ -78,8 +78,6 @@ CONDITIONAL_REQUIREMENTS = {
|
||||||
"matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
|
"matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
|
||||||
# we use execute_batch, which arrived in psycopg 2.7.
|
# we use execute_batch, which arrived in psycopg 2.7.
|
||||||
"postgres": ["psycopg2>=2.7"],
|
"postgres": ["psycopg2>=2.7"],
|
||||||
# ConsentResource uses select_autoescape, which arrived in jinja 2.9
|
|
||||||
"resources.consent": ["Jinja2>=2.9"],
|
|
||||||
# ACME support is required to provision TLS certificates from authorities
|
# ACME support is required to provision TLS certificates from authorities
|
||||||
# that use the protocol, such as Let's Encrypt.
|
# that use the protocol, such as Let's Encrypt.
|
||||||
"acme": [
|
"acme": [
|
||||||
|
|
|
@ -32,7 +32,7 @@ from synapse.http.servlet import (
|
||||||
parse_json_object_from_request,
|
parse_json_object_from_request,
|
||||||
parse_string,
|
parse_string,
|
||||||
)
|
)
|
||||||
from synapse.push.mailer import Mailer, load_jinja2_templates
|
from synapse.push.mailer import Mailer
|
||||||
from synapse.util.msisdn import phone_number_to_msisdn
|
from synapse.util.msisdn import phone_number_to_msisdn
|
||||||
from synapse.util.stringutils import assert_valid_client_secret, random_string
|
from synapse.util.stringutils import assert_valid_client_secret, random_string
|
||||||
from synapse.util.threepids import canonicalise_email, check_3pid_allowed
|
from synapse.util.threepids import canonicalise_email, check_3pid_allowed
|
||||||
|
@ -53,21 +53,11 @@ class EmailPasswordRequestTokenRestServlet(RestServlet):
|
||||||
self.identity_handler = hs.get_handlers().identity_handler
|
self.identity_handler = hs.get_handlers().identity_handler
|
||||||
|
|
||||||
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
template_html, template_text = load_jinja2_templates(
|
|
||||||
self.config.email_template_dir,
|
|
||||||
[
|
|
||||||
self.config.email_password_reset_template_html,
|
|
||||||
self.config.email_password_reset_template_text,
|
|
||||||
],
|
|
||||||
apply_format_ts_filter=True,
|
|
||||||
apply_mxc_to_http_filter=True,
|
|
||||||
public_baseurl=self.config.public_baseurl,
|
|
||||||
)
|
|
||||||
self.mailer = Mailer(
|
self.mailer = Mailer(
|
||||||
hs=self.hs,
|
hs=self.hs,
|
||||||
app_name=self.config.email_app_name,
|
app_name=self.config.email_app_name,
|
||||||
template_html=template_html,
|
template_html=self.config.email_password_reset_template_html,
|
||||||
template_text=template_text,
|
template_text=self.config.email_password_reset_template_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_POST(self, request):
|
async def on_POST(self, request):
|
||||||
|
@ -169,9 +159,8 @@ class PasswordResetSubmitTokenServlet(RestServlet):
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
(self.failure_email_template,) = load_jinja2_templates(
|
self._failure_email_template = (
|
||||||
self.config.email_template_dir,
|
self.config.email_password_reset_template_failure_html
|
||||||
[self.config.email_password_reset_template_failure_html],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_GET(self, request, medium):
|
async def on_GET(self, request, medium):
|
||||||
|
@ -214,14 +203,14 @@ class PasswordResetSubmitTokenServlet(RestServlet):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Otherwise show the success template
|
# Otherwise show the success template
|
||||||
html = self.config.email_password_reset_template_success_html
|
html = self.config.email_password_reset_template_success_html_content
|
||||||
status_code = 200
|
status_code = 200
|
||||||
except ThreepidValidationError as e:
|
except ThreepidValidationError as e:
|
||||||
status_code = e.code
|
status_code = e.code
|
||||||
|
|
||||||
# Show a failure page with a reason
|
# Show a failure page with a reason
|
||||||
template_vars = {"failure_reason": e.msg}
|
template_vars = {"failure_reason": e.msg}
|
||||||
html = self.failure_email_template.render(**template_vars)
|
html = self._failure_email_template.render(**template_vars)
|
||||||
|
|
||||||
respond_with_html(request, status_code, html)
|
respond_with_html(request, status_code, html)
|
||||||
|
|
||||||
|
@ -411,19 +400,11 @@ class EmailThreepidRequestTokenRestServlet(RestServlet):
|
||||||
self.store = self.hs.get_datastore()
|
self.store = self.hs.get_datastore()
|
||||||
|
|
||||||
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
template_html, template_text = load_jinja2_templates(
|
|
||||||
self.config.email_template_dir,
|
|
||||||
[
|
|
||||||
self.config.email_add_threepid_template_html,
|
|
||||||
self.config.email_add_threepid_template_text,
|
|
||||||
],
|
|
||||||
public_baseurl=self.config.public_baseurl,
|
|
||||||
)
|
|
||||||
self.mailer = Mailer(
|
self.mailer = Mailer(
|
||||||
hs=self.hs,
|
hs=self.hs,
|
||||||
app_name=self.config.email_app_name,
|
app_name=self.config.email_app_name,
|
||||||
template_html=template_html,
|
template_html=self.config.email_add_threepid_template_html,
|
||||||
template_text=template_text,
|
template_text=self.config.email_add_threepid_template_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_POST(self, request):
|
async def on_POST(self, request):
|
||||||
|
@ -578,9 +559,8 @@ class AddThreepidEmailSubmitTokenServlet(RestServlet):
|
||||||
self.clock = hs.get_clock()
|
self.clock = hs.get_clock()
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
(self.failure_email_template,) = load_jinja2_templates(
|
self._failure_email_template = (
|
||||||
self.config.email_template_dir,
|
self.config.email_add_threepid_template_failure_html
|
||||||
[self.config.email_add_threepid_template_failure_html],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_GET(self, request):
|
async def on_GET(self, request):
|
||||||
|
@ -631,7 +611,7 @@ class AddThreepidEmailSubmitTokenServlet(RestServlet):
|
||||||
|
|
||||||
# Show a failure page with a reason
|
# Show a failure page with a reason
|
||||||
template_vars = {"failure_reason": e.msg}
|
template_vars = {"failure_reason": e.msg}
|
||||||
html = self.failure_email_template.render(**template_vars)
|
html = self._failure_email_template.render(**template_vars)
|
||||||
|
|
||||||
respond_with_html(request, status_code, html)
|
respond_with_html(request, status_code, html)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ from synapse.http.servlet import (
|
||||||
parse_json_object_from_request,
|
parse_json_object_from_request,
|
||||||
parse_string,
|
parse_string,
|
||||||
)
|
)
|
||||||
from synapse.push.mailer import load_jinja2_templates
|
from synapse.push.mailer import Mailer
|
||||||
from synapse.util.msisdn import phone_number_to_msisdn
|
from synapse.util.msisdn import phone_number_to_msisdn
|
||||||
from synapse.util.ratelimitutils import FederationRateLimiter
|
from synapse.util.ratelimitutils import FederationRateLimiter
|
||||||
from synapse.util.stringutils import assert_valid_client_secret, random_string
|
from synapse.util.stringutils import assert_valid_client_secret, random_string
|
||||||
|
@ -81,23 +81,11 @@ class EmailRegisterRequestTokenRestServlet(RestServlet):
|
||||||
self.config = hs.config
|
self.config = hs.config
|
||||||
|
|
||||||
if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
from synapse.push.mailer import Mailer, load_jinja2_templates
|
|
||||||
|
|
||||||
template_html, template_text = load_jinja2_templates(
|
|
||||||
self.config.email_template_dir,
|
|
||||||
[
|
|
||||||
self.config.email_registration_template_html,
|
|
||||||
self.config.email_registration_template_text,
|
|
||||||
],
|
|
||||||
apply_format_ts_filter=True,
|
|
||||||
apply_mxc_to_http_filter=True,
|
|
||||||
public_baseurl=self.config.public_baseurl,
|
|
||||||
)
|
|
||||||
self.mailer = Mailer(
|
self.mailer = Mailer(
|
||||||
hs=self.hs,
|
hs=self.hs,
|
||||||
app_name=self.config.email_app_name,
|
app_name=self.config.email_app_name,
|
||||||
template_html=template_html,
|
template_html=self.config.email_registration_template_html,
|
||||||
template_text=template_text,
|
template_text=self.config.email_registration_template_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_POST(self, request):
|
async def on_POST(self, request):
|
||||||
|
@ -262,15 +250,8 @@ class RegistrationSubmitTokenServlet(RestServlet):
|
||||||
self.store = hs.get_datastore()
|
self.store = hs.get_datastore()
|
||||||
|
|
||||||
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
(self.failure_email_template,) = load_jinja2_templates(
|
self._failure_email_template = (
|
||||||
self.config.email_template_dir,
|
self.config.email_registration_template_failure_html
|
||||||
[self.config.email_registration_template_failure_html],
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
|
||||||
(self.failure_email_template,) = load_jinja2_templates(
|
|
||||||
self.config.email_template_dir,
|
|
||||||
[self.config.email_registration_template_failure_html],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_GET(self, request, medium):
|
async def on_GET(self, request, medium):
|
||||||
|
@ -318,7 +299,7 @@ class RegistrationSubmitTokenServlet(RestServlet):
|
||||||
|
|
||||||
# Show a failure page with a reason
|
# Show a failure page with a reason
|
||||||
template_vars = {"failure_reason": e.msg}
|
template_vars = {"failure_reason": e.msg}
|
||||||
html = self.failure_email_template.render(**template_vars)
|
html = self._failure_email_template.render(**template_vars)
|
||||||
|
|
||||||
respond_with_html(request, status_code, html)
|
respond_with_html(request, status_code, html)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from synapse.config import ConfigError
|
||||||
|
from synapse.util.stringutils import random_string
|
||||||
|
|
||||||
|
from tests import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfigTestCase(unittest.HomeserverTestCase):
|
||||||
|
def prepare(self, reactor, clock, hs):
|
||||||
|
self.hs = hs
|
||||||
|
|
||||||
|
def test_loading_missing_templates(self):
|
||||||
|
# Use a temporary directory that exists on the system, but that isn't likely to
|
||||||
|
# contain template files
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
# Attempt to load an HTML template from our custom template directory
|
||||||
|
template = self.hs.config.read_templates(["sso_error.html"], tmp_dir)[0]
|
||||||
|
|
||||||
|
# If no errors, we should've gotten the default template instead
|
||||||
|
|
||||||
|
# Render the template
|
||||||
|
a_random_string = random_string(5)
|
||||||
|
html_content = template.render({"error_description": a_random_string})
|
||||||
|
|
||||||
|
# Check that our string exists in the template
|
||||||
|
self.assertIn(
|
||||||
|
a_random_string,
|
||||||
|
html_content,
|
||||||
|
"Template file did not contain our test string",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_loading_custom_templates(self):
|
||||||
|
# Use a temporary directory that exists on the system
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
# Create a temporary bogus template file
|
||||||
|
with tempfile.NamedTemporaryFile(dir=tmp_dir) as tmp_template:
|
||||||
|
# Get temporary file's filename
|
||||||
|
template_filename = os.path.basename(tmp_template.name)
|
||||||
|
|
||||||
|
# Write a custom HTML template
|
||||||
|
contents = b"{{ test_variable }}"
|
||||||
|
tmp_template.write(contents)
|
||||||
|
tmp_template.flush()
|
||||||
|
|
||||||
|
# Attempt to load the template from our custom template directory
|
||||||
|
template = (
|
||||||
|
self.hs.config.read_templates([template_filename], tmp_dir)
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
# Render the template
|
||||||
|
a_random_string = random_string(5)
|
||||||
|
html_content = template.render({"test_variable": a_random_string})
|
||||||
|
|
||||||
|
# Check that our string exists in the template
|
||||||
|
self.assertIn(
|
||||||
|
a_random_string,
|
||||||
|
html_content,
|
||||||
|
"Template file did not contain our test string",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_loading_template_from_nonexistent_custom_directory(self):
|
||||||
|
with self.assertRaises(ConfigError):
|
||||||
|
self.hs.config.read_templates(
|
||||||
|
["some_filename.html"], "a_nonexistent_directory"
|
||||||
|
)
|
Loading…
Reference in New Issue