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,