diff --git a/frappe/email/doctype/email_template/email_template.py b/frappe/email/doctype/email_template/email_template.py index fcc6ce5010..1ef8ec062b 100644 --- a/frappe/email/doctype/email_template/email_template.py +++ b/frappe/email/doctype/email_template/email_template.py @@ -9,33 +9,33 @@ from frappe.utils.jinja import validate_template class EmailTemplate(Document): + @property + def response_(self): + return self.response_html if self.use_html else self.response + def validate(self): - if self.use_html: - validate_template(self.response_html) - else: - validate_template(self.response) + validate_template(self.subject) + validate_template(self.response_) def get_formatted_subject(self, doc): return frappe.render_template(self.subject, doc) def get_formatted_response(self, doc): - if self.use_html: - return frappe.render_template(self.response_html, doc) - - return frappe.render_template(self.response, doc) + return frappe.render_template(self.response_, doc) def get_formatted_email(self, doc): if isinstance(doc, str): doc = json.loads(doc) - return {"subject": self.get_formatted_subject(doc), "message": self.get_formatted_response(doc)} + return { + "subject": self.get_formatted_subject(doc), + "message": self.get_formatted_response(doc), + } @frappe.whitelist() def get_email_template(template_name, doc): """Returns the processed HTML of a email template with the given doc""" - if isinstance(doc, str): - doc = json.loads(doc) email_template = frappe.get_doc("Email Template", template_name) return email_template.get_formatted_email(doc) diff --git a/frappe/public/js/frappe/form/controls/base_input.js b/frappe/public/js/frappe/form/controls/base_input.js index 43fb4f54dc..0b9da726d7 100644 --- a/frappe/public/js/frappe/form/controls/base_input.js +++ b/frappe/public/js/frappe/form/controls/base_input.js @@ -109,7 +109,7 @@ frappe.ui.form.ControlInput = class ControlInput extends frappe.ui.form.Control "title", __( "This value is fetched from {0}'s {1} field", - me.df.fetch_from.split(".") + me.df.fetch_from.split(".").map((value) => __(frappe.unscrub(value))) ) ); } diff --git a/frappe/query_builder/utils.py b/frappe/query_builder/utils.py index be0403a291..91cdfd0a54 100644 --- a/frappe/query_builder/utils.py +++ b/frappe/query_builder/utils.py @@ -104,9 +104,8 @@ def patch_query_execute(): # frame1: execute_query() # frame2: frame that called `query.run()` # - # if frame2 is server script it wont have a filename and hence + # if frame2 is server script is set as the filename # it shouldn't be allowed. - # p.s. stack() returns `""` as filename if not a file. pass else: raise frappe.PermissionError("Only SELECT SQL allowed in scripting") diff --git a/frappe/tests/test_safe_exec.py b/frappe/tests/test_safe_exec.py index fcd5832680..ad09aaea59 100644 --- a/frappe/tests/test_safe_exec.py +++ b/frappe/tests/test_safe_exec.py @@ -75,3 +75,10 @@ class TestSafeExec(FrappeTestCase): def test_unsafe_objects(self): unsafe_global = {"frappe": frappe} self.assertRaises(SyntaxError, safe_exec, """frappe.msgprint("Hello")""", unsafe_global) + + def test_attrdict(self): + # jinja + frappe.render_template("{% set my_dict = _dict() %} {{- my_dict.works -}}") + + # RestrictedPython + safe_exec("my_dict = _dict()") diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index fd81f8e87c..ffda3ca127 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -4828,3 +4828,4 @@ CSV Quoting,Anführungszeichen, CSV Preview,Vorschau, Non-numeric,Nicht-numerische, Minimal,Minimal, +This value is fetched from {0}'s {1} field,Dieser Wert ergibt sich aus dem Feld {1} von {0}, diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 5e3a70554d..33bb929bc4 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -60,7 +60,7 @@ def validate_template(html): frappe.throw(frappe._("Syntax error in template")) -def render_template(template, context, is_path=None, safe_render=True): +def render_template(template, context=None, is_path=None, safe_render=True): """Render a template using Jinja :param template: path or HTML containing the jinja template @@ -76,6 +76,9 @@ def render_template(template, context, is_path=None, safe_render=True): if not template: return "" + if context is None: + context = {} + if is_path or guess_is_path(template): return get_jenv().get_template(template).render(context) else: diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 890f16a1c3..9e99754c67 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -8,6 +8,7 @@ from functools import lru_cache import RestrictedPython.Guards from RestrictedPython import compile_restricted, safe_globals +from RestrictedPython.transformer import RestrictingNodeTransformer import frappe import frappe.exceptions @@ -45,6 +46,14 @@ class NamespaceDict(frappe._dict): return ret +class FrappeTransformer(RestrictingNodeTransformer): + def check_name(self, node, name, *args, **kwargs): + if name == "_dict": + return + + return super().check_name(node, name, *args, **kwargs) + + def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=False): # server scripts can be disabled via site_config.json # they are enabled by default @@ -69,7 +78,11 @@ def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=Fals with safe_exec_flags(), patched_qb(): # execute script compiled by RestrictedPython - exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used + exec( + compile_restricted(script, filename="", policy=FrappeTransformer), + exec_globals, + _locals, + ) return exec_globals, _locals @@ -106,6 +119,7 @@ def get_safe_globals(): as_json=frappe.as_json, dict=dict, log=frappe.log, + _dict=frappe._dict, args=form_dict, frappe=NamespaceDict( call=call_whitelisted_function, @@ -116,7 +130,6 @@ def get_safe_globals(): time_format=time_format, format_date=frappe.utils.data.global_date_format, form_dict=form_dict, - as_dict=frappe._dict, bold=frappe.bold, copy_doc=frappe.copy_doc, errprint=frappe.errprint, diff --git a/frappe/website/doctype/website_theme/website_theme.py b/frappe/website/doctype/website_theme/website_theme.py index ca78adf94e..7abfab93e3 100644 --- a/frappe/website/doctype/website_theme/website_theme.py +++ b/frappe/website/doctype/website_theme/website_theme.py @@ -62,10 +62,6 @@ class WebsiteTheme(Document): def generate_bootstrap_theme(self): from subprocess import PIPE, Popen - self.theme_scss = frappe.render_template( - "frappe/website/doctype/website_theme/website_theme_template.scss", self.as_dict() - ) - # create theme file in site public files folder folder_path = abspath(frappe.utils.get_files_path("website_theme", is_private=False)) # create folder if not exist