diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 839b784651..ded397d5e3 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -4,6 +4,8 @@ from __future__ import unicode_literals +import ast + import frappe from frappe.model.document import Document from frappe.utils.safe_exec import safe_exec @@ -11,9 +13,9 @@ from frappe import _ class ServerScript(Document): - @staticmethod - def validate(): + def validate(self): frappe.only_for('Script Manager', True) + ast.parse(self.script) @staticmethod def on_update(): diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index 3356e584af..256cea57d7 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -45,6 +45,15 @@ frappe.response['message'] = 'hello' allow_guest = 1, script = ''' frappe.flags = 'hello' +''' + ), + dict( + name='test_invalid_namespace_method', + script_type = 'DocType Event', + doctype_event = 'Before Insert', + reference_doctype = 'Note', + script = ''' +frappe.method_that_doesnt_exist("do some magic") ''' ) ] @@ -85,3 +94,8 @@ class TestServerScript(unittest.TestCase): def test_api_return(self): self.assertEqual(frappe.get_doc('Server Script', 'test_return_value').execute_method(), 'hello') + + def test_attribute_error(self): + """Raise AttributeError if method not found in Namespace""" + note = frappe.get_doc({"doctype": "Note", "title": "Test Note: Server Script"}) + self.assertRaises(AttributeError, note.insert) diff --git a/frappe/public/js/frappe/views/reports/report_utils.js b/frappe/public/js/frappe/views/reports/report_utils.js index 158dbd653b..ebb08bd24b 100644 --- a/frappe/public/js/frappe/views/reports/report_utils.js +++ b/frappe/public/js/frappe/views/reports/report_utils.js @@ -15,7 +15,9 @@ frappe.report_utils = { if (raw_data.add_total_row) { labels = labels.slice(0, -1); - datasets[0].values = datasets[0].values.slice(0, -1); + datasets.forEach(dataset => { + dataset.values = dataset.values.slice(0, -1); + }); } return { diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index cc5b42acb7..5ac4de618d 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -66,9 +66,14 @@ def get_email_address(user=None): def get_formatted_email(user, mail=None): """get Email Address of user formatted as: `John Doe `""" fullname = get_fullname(user) + if not mail: - mail = get_email_address(user) - return cstr(make_header(decode_header(formataddr((fullname, mail))))) + mail = get_email_address(user) or validate_email_address(user) + + if not mail: + return '' + else: + return cstr(make_header(decode_header(formataddr((fullname, mail))))) def extract_email_id(email): """fetch only the email part of the Email Address""" diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index fee6b404ac..2aacf5eda8 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -13,7 +13,19 @@ from frappe.www.printview import get_visible_columns import frappe.exceptions import frappe.integrations.utils -class ServerScriptNotEnabled(frappe.PermissionError): pass +class ServerScriptNotEnabled(frappe.PermissionError): + pass + +class NamespaceDict(frappe._dict): + """Raise AttributeError if function not found in namespace""" + def __getattr__(self, key): + ret = self.get(key) + if (not ret and key.startswith("__")) or (key not in self): + def default_function(*args, **kwargs): + raise AttributeError(f"module has no attribute '{key}'") + return default_function + return ret + def safe_exec(script, _globals=None, _locals=None): # script reports must be enabled via site_config.json @@ -46,13 +58,13 @@ def get_safe_globals(): user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest" - out = frappe._dict( + out = NamespaceDict( # make available limited methods of frappe json=json, dict=dict, log=frappe.log, _dict=frappe._dict, - frappe=frappe._dict( + frappe=NamespaceDict( flags=frappe._dict(), format=frappe.format_value, format_value=frappe.format_value, @@ -112,7 +124,7 @@ def get_safe_globals(): out.get_visible_columns = get_visible_columns out.frappe.date_format = date_format out.frappe.time_format = time_format - out.frappe.db = frappe._dict( + out.frappe.db = NamespaceDict( get_list = frappe.get_list, get_all = frappe.get_all, get_value = frappe.db.get_value,