From f2e1dbe7eb677ea3007bf8bb42ec94aba397cf2b Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 7 Dec 2022 13:13:27 +0530 Subject: [PATCH 01/10] fix: restore `_dict`, used in Jinja code --- frappe/tests/test_safe_exec.py | 3 +++ frappe/utils/safe_exec.py | 1 + 2 files changed, 4 insertions(+) diff --git a/frappe/tests/test_safe_exec.py b/frappe/tests/test_safe_exec.py index fcd5832680..1e370a1160 100644 --- a/frappe/tests/test_safe_exec.py +++ b/frappe/tests/test_safe_exec.py @@ -75,3 +75,6 @@ class TestSafeExec(FrappeTestCase): def test_unsafe_objects(self): unsafe_global = {"frappe": frappe} self.assertRaises(SyntaxError, safe_exec, """frappe.msgprint("Hello")""", unsafe_global) + + def test_frappe_dict_in_jinja(self): + frappe.render_template("{% set my_dict = _dict() %} {{- my_dict.works -}}", {}) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 890f16a1c3..a362664aad 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -105,6 +105,7 @@ def get_safe_globals(): json=NamespaceDict(loads=json.loads, dumps=json.dumps), as_json=frappe.as_json, dict=dict, + _dict=frappe._dict, # this isn't usable with RestrictedPython, but kept for Jinja compatibility log=frappe.log, args=form_dict, frappe=NamespaceDict( From 10695d3d496bd6f8a28e77eef675d161cb4909f6 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 7 Dec 2022 13:32:38 +0530 Subject: [PATCH 02/10] feat: make context optional when calling `render_template` --- frappe/tests/test_safe_exec.py | 2 +- frappe/utils/jinja.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_safe_exec.py b/frappe/tests/test_safe_exec.py index 1e370a1160..15d898b716 100644 --- a/frappe/tests/test_safe_exec.py +++ b/frappe/tests/test_safe_exec.py @@ -77,4 +77,4 @@ class TestSafeExec(FrappeTestCase): self.assertRaises(SyntaxError, safe_exec, """frappe.msgprint("Hello")""", unsafe_global) def test_frappe_dict_in_jinja(self): - frappe.render_template("{% set my_dict = _dict() %} {{- my_dict.works -}}", {}) + frappe.render_template("{% set my_dict = _dict() %} {{- my_dict.works -}}") diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 5e3a70554d..01ab5ab48c 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 not context: + context = {} + if is_path or guess_is_path(template): return get_jenv().get_template(template).render(context) else: From 7d47d1069214873c86775746b77e800e284507a1 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 7 Dec 2022 13:45:28 +0530 Subject: [PATCH 03/10] fix: override RestrictedPython transformer to allow `_dict`, revert `frappe.as_dict` --- frappe/tests/test_safe_exec.py | 6 +++++- frappe/utils/safe_exec.py | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/frappe/tests/test_safe_exec.py b/frappe/tests/test_safe_exec.py index 15d898b716..ad09aaea59 100644 --- a/frappe/tests/test_safe_exec.py +++ b/frappe/tests/test_safe_exec.py @@ -76,5 +76,9 @@ class TestSafeExec(FrappeTestCase): unsafe_global = {"frappe": frappe} self.assertRaises(SyntaxError, safe_exec, """frappe.msgprint("Hello")""", unsafe_global) - def test_frappe_dict_in_jinja(self): + 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/utils/safe_exec.py b/frappe/utils/safe_exec.py index a362664aad..c31c1b72d4 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,9 @@ 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, policy=FrappeTransformer), exec_globals, _locals + ) # pylint: disable=exec-used return exec_globals, _locals @@ -105,7 +116,7 @@ def get_safe_globals(): json=NamespaceDict(loads=json.loads, dumps=json.dumps), as_json=frappe.as_json, dict=dict, - _dict=frappe._dict, # this isn't usable with RestrictedPython, but kept for Jinja compatibility + _dict=frappe._dict, log=frappe.log, args=form_dict, frappe=NamespaceDict( @@ -117,7 +128,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, From aa53779e6b2abf9723b6c9460da8b2da6a0dc79a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 7 Dec 2022 13:46:34 +0530 Subject: [PATCH 04/10] fix: improve condition to init context --- frappe/utils/jinja.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 01ab5ab48c..33bb929bc4 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -76,7 +76,7 @@ def render_template(template, context=None, is_path=None, safe_render=True): if not template: return "" - if not context: + if context is None: context = {} if is_path or guess_is_path(template): From 0c220169da7700a2eef0be87cbd2d10ff017161e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 7 Dec 2022 13:50:59 +0530 Subject: [PATCH 05/10] chore: reorder pylint disable --- frappe/utils/safe_exec.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index c31c1b72d4..01025c98bb 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -78,9 +78,8 @@ 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, policy=FrappeTransformer), exec_globals, _locals - ) # pylint: disable=exec-used + # pylint: disable-next=exec-used + exec(compile_restricted(script, policy=FrappeTransformer), exec_globals, _locals) return exec_globals, _locals From 5f2cc8ec79f672ebf408d26d4ad466a92fa57871 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 7 Dec 2022 14:09:02 +0530 Subject: [PATCH 06/10] chore: keep previous order for easy backport --- frappe/utils/safe_exec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 01025c98bb..053793af03 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -115,8 +115,8 @@ def get_safe_globals(): json=NamespaceDict(loads=json.loads, dumps=json.dumps), as_json=frappe.as_json, dict=dict, - _dict=frappe._dict, log=frappe.log, + _dict=frappe._dict, args=form_dict, frappe=NamespaceDict( call=call_whitelisted_function, From dcae57475d2b26b3e343938bd02e30387919786f Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Dec 2022 10:27:07 +0100 Subject: [PATCH 07/10] fix: unscrub and translate field names in tooltip explaining fetch from (#19143) --- frappe/public/js/frappe/form/controls/base_input.js | 2 +- frappe/translations/de.csv | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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/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}, From d61748446bfa39c862d951cc7b7a5036846fbecb Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 7 Dec 2022 09:39:07 +0000 Subject: [PATCH 08/10] fix: validate email template subject and minor refactor (#19102) * fix: validate email template subject * chore: remove duplicate call to `json.loads` and minor refactor --- .../doctype/email_template/email_template.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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) From 02601dafd4c3de5d7d537cbb225af976033adf9a Mon Sep 17 00:00:00 2001 From: Mohammad Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:11:46 +0530 Subject: [PATCH 09/10] perf: remove redundant render template (#19123) --- frappe/website/doctype/website_theme/website_theme.py | 4 ---- 1 file changed, 4 deletions(-) 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 From b11793ab02ad2397072e80cb732278832ac5969b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 7 Dec 2022 16:34:15 +0530 Subject: [PATCH 10/10] fix: set filename explicitly for safe_exec --- frappe/query_builder/utils.py | 3 +-- frappe/utils/safe_exec.py | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) 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/utils/safe_exec.py b/frappe/utils/safe_exec.py index 053793af03..9e99754c67 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -78,8 +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 - # pylint: disable-next=exec-used - exec(compile_restricted(script, policy=FrappeTransformer), exec_globals, _locals) + exec( + compile_restricted(script, filename="", policy=FrappeTransformer), + exec_globals, + _locals, + ) return exec_globals, _locals