Преглед изворни кода

Merge branch 'develop' into container-js-fix

version-14
Suraj Shetty пре 3 година
committed by GitHub
родитељ
комит
7cb293799a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
14 измењених фајлова са 568 додато и 47 уклоњено
  1. +14
    -5
      frappe/__init__.py
  2. +1
    -0
      frappe/client.py
  3. +0
    -7
      frappe/core/doctype/server_script/server_script_utils.py
  4. +168
    -0
      frappe/custom/fixtures/temp_doctype.json
  5. +168
    -0
      frappe/custom/fixtures/temp_singles.json
  6. +17
    -3
      frappe/handler.py
  7. +1
    -1
      frappe/integrations/doctype/webhook/__init__.py
  8. +10
    -5
      frappe/model/db_query.py
  9. +3
    -0
      frappe/public/js/frappe/form/controls/datetime.js
  10. +19
    -16
      frappe/public/scss/desk/page.scss
  11. +81
    -0
      frappe/tests/test_fixture_import.py
  12. +24
    -1
      frappe/tests/test_safe_exec.py
  13. +60
    -7
      frappe/utils/safe_exec.py
  14. +2
    -2
      frappe/www/404.html

+ 14
- 5
frappe/__init__.py Прегледај датотеку

@@ -740,17 +740,26 @@ def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=Fals
:param doc: [optional] Checks User permissions for given doc.
:param user: [optional] Check for given user. Default: current user.
:param parent_doctype: Required when checking permission for a child DocType (unless doc is specified)."""
import frappe.permissions

if not doctype and doc:
doctype = doc.doctype

import frappe.permissions
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user,
raise_exception=throw, parent_doctype=parent_doctype)

if throw and not out:
if doc:
frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name))
else:
frappe.throw(_("No permission for {0}").format(doctype))
# mimics frappe.throw
document_label = f"{doc.doctype} {doc.name}" if doc else doctype
msgprint(
_("No permission for {0}").format(document_label),
raise_exception=ValidationError,
title=None,
indicator='red',
is_minimizable=None,
wide=None,
as_list=False
)

return out



+ 1
- 0
frappe/client.py Прегледај датотеку

@@ -32,6 +32,7 @@ def get_list(doctype, fields=None, filters=None, order_by=None,

args = frappe._dict(
doctype=doctype,
parent_doctype=parent,
fields=fields,
filters=filters,
or_filters=or_filters,


+ 0
- 7
frappe/core/doctype/server_script/server_script_utils.py Прегледај датотеку

@@ -19,13 +19,6 @@ EVENT_MAP = {
'on_update_after_submit': 'After Save (Submitted Document)'
}

def run_server_script_api(method):
# called via handler, execute an API script
script_name = get_server_script_map().get('_api', {}).get(method)
if script_name:
frappe.get_doc('Server Script', script_name).execute_method()
return True

def run_server_script_for_doc_event(doc, event):
# run document event method
if not event in EVENT_MAP:


+ 168
- 0
frappe/custom/fixtures/temp_doctype.json Прегледај датотеку

@@ -0,0 +1,168 @@
{
"docstatus": 0,
"doctype": "DocType",
"name": "new-doctype-2",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"is_submittable": 0,
"istable": 0,
"issingle": 0,
"is_tree": 0,
"editable_grid": 1,
"quick_entry": 1,
"track_changes": 1,
"track_seen": 0,
"track_views": 0,
"custom": 1,
"beta": 0,
"is_virtual": 0,
"naming_rule": "",
"name_case": "",
"allow_rename": 1,
"hide_toolbar": 0,
"allow_copy": 0,
"allow_import": 0,
"allow_events_in_timeline": 0,
"allow_auto_repeat": 0,
"sort_field": "modified",
"sort_order": "DESC",
"document_type": "",
"show_preview_popup": 0,
"show_name_in_global_search": 0,
"email_append_to": 0,
"read_only": 0,
"in_create": 0,
"has_web_view": 0,
"allow_guest_to_view": 0,
"index_web_pages_for_search": 1,
"engine": "InnoDB",
"permissions": [
{
"docstatus": 0,
"doctype": "DocPerm",
"name": "new-docperm-2",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"if_owner": 0,
"permlevel": 0,
"select": 0,
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
"submit": 0,
"cancel": 0,
"amend": 0,
"report": 1,
"export": 1,
"import": 0,
"set_user_permissions": 0,
"share": 1,
"print": 1,
"email": 1,
"parent": "new-doctype-2",
"parentfield": "permissions",
"parenttype": "DocType",
"idx": 1,
"role": "System Manager"
}
],
"__newname": "temp_doctype",
"module": "Custom",
"fields": [
{
"docstatus": 0,
"doctype": "DocField",
"name": "new-docfield-1",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"fieldtype": "Data",
"precision": "",
"non_negative": 0,
"hide_days": 0,
"hide_seconds": 0,
"reqd": 1,
"search_index": 0,
"fetch_if_empty": 0,
"hidden": 0,
"bold": 0,
"allow_in_quick_entry": 0,
"translatable": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"report_hide": 0,
"collapsible": 0,
"hide_border": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"in_preview": 0,
"in_filter": 0,
"in_global_search": 0,
"read_only": 0,
"allow_on_submit": 0,
"ignore_user_permissions": 0,
"allow_bulk_edit": 0,
"permlevel": 0,
"ignore_xss_filter": 0,
"unique": 0,
"no_copy": 0,
"set_only_once": 0,
"remember_last_selected_value": 0,
"parent": "new-doctype-2",
"parentfield": "fields",
"parenttype": "DocType",
"idx": 1,
"__unedited": false,
"label": "member_name"
},
{
"docstatus": 0,
"doctype": "DocField",
"name": "new-docfield-2",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"fieldtype": "Data",
"precision": "",
"non_negative": 0,
"hide_days": 0,
"hide_seconds": 0,
"reqd": 0,
"search_index": 0,
"fetch_if_empty": 0,
"hidden": 0,
"bold": 0,
"allow_in_quick_entry": 0,
"translatable": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"report_hide": 0,
"collapsible": 0,
"hide_border": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"in_preview": 0,
"in_filter": 0,
"in_global_search": 0,
"read_only": 0,
"allow_on_submit": 0,
"ignore_user_permissions": 0,
"allow_bulk_edit": 0,
"permlevel": 0,
"ignore_xss_filter": 0,
"unique": 0,
"no_copy": 0,
"set_only_once": 0,
"remember_last_selected_value": 0,
"parent": "new-doctype-2",
"parentfield": "fields",
"parenttype": "DocType",
"idx": 2,
"__unedited": false,
"label": "email"
}
]
}

+ 168
- 0
frappe/custom/fixtures/temp_singles.json Прегледај датотеку

@@ -0,0 +1,168 @@
{
"docstatus": 0,
"doctype": "DocType",
"name": "new-doctype-1",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"is_submittable": 0,
"istable": 0,
"issingle": 1,
"is_tree": 0,
"editable_grid": 1,
"quick_entry": 0,
"track_changes": 1,
"track_seen": 0,
"track_views": 0,
"custom": 1,
"beta": 0,
"is_virtual": 0,
"naming_rule": "",
"name_case": "",
"allow_rename": 1,
"hide_toolbar": 0,
"allow_copy": 0,
"allow_import": 0,
"allow_events_in_timeline": 0,
"allow_auto_repeat": 0,
"sort_field": "modified",
"sort_order": "DESC",
"document_type": "",
"show_preview_popup": 0,
"show_name_in_global_search": 0,
"email_append_to": 0,
"read_only": 0,
"in_create": 0,
"has_web_view": 0,
"allow_guest_to_view": 0,
"index_web_pages_for_search": 1,
"engine": "InnoDB",
"permissions": [
{
"docstatus": 0,
"doctype": "DocPerm",
"name": "new-docperm-1",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"if_owner": 0,
"permlevel": 0,
"select": 0,
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
"submit": 0,
"cancel": 0,
"amend": 0,
"report": 1,
"export": 1,
"import": 0,
"set_user_permissions": 0,
"share": 1,
"print": 1,
"email": 1,
"parent": "new-doctype-1",
"parentfield": "permissions",
"parenttype": "DocType",
"idx": 1,
"role": "System Manager"
}
],
"__newname": "temp_singles",
"module": "Custom",
"fields": [
{
"docstatus": 0,
"doctype": "DocField",
"name": "new-docfield-1",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"fieldtype": "Data",
"precision": "",
"non_negative": 0,
"hide_days": 0,
"hide_seconds": 0,
"reqd": 0,
"search_index": 0,
"fetch_if_empty": 0,
"hidden": 0,
"bold": 0,
"allow_in_quick_entry": 0,
"translatable": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"report_hide": 0,
"collapsible": 0,
"hide_border": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"in_preview": 0,
"in_filter": 0,
"in_global_search": 0,
"read_only": 0,
"allow_on_submit": 0,
"ignore_user_permissions": 0,
"allow_bulk_edit": 0,
"permlevel": 0,
"ignore_xss_filter": 0,
"unique": 0,
"no_copy": 0,
"set_only_once": 0,
"remember_last_selected_value": 0,
"parent": "new-doctype-1",
"parentfield": "fields",
"parenttype": "DocType",
"idx": 1,
"__unedited": false,
"label": "member_name"
},
{
"docstatus": 0,
"doctype": "DocField",
"name": "new-docfield-2",
"__islocal": 1,
"__unsaved": 1,
"owner": "Administrator",
"fieldtype": "Data",
"precision": "",
"non_negative": 0,
"hide_days": 0,
"hide_seconds": 0,
"reqd": 0,
"search_index": 0,
"fetch_if_empty": 0,
"hidden": 0,
"bold": 0,
"allow_in_quick_entry": 0,
"translatable": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"report_hide": 0,
"collapsible": 0,
"hide_border": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"in_preview": 0,
"in_filter": 0,
"in_global_search": 0,
"read_only": 0,
"allow_on_submit": 0,
"ignore_user_permissions": 0,
"allow_bulk_edit": 0,
"permlevel": 0,
"ignore_xss_filter": 0,
"unique": 0,
"no_copy": 0,
"set_only_once": 0,
"remember_last_selected_value": 0,
"parent": "new-doctype-1",
"parentfield": "fields",
"parenttype": "DocType",
"idx": 2,
"__unedited": false,
"label": "email"
}
]
}

+ 17
- 3
frappe/handler.py Прегледај датотеку

@@ -12,7 +12,7 @@ from frappe.utils.response import build_response
from frappe.utils.csvutils import build_csv_response
from frappe.utils.image import optimize_image
from mimetypes import guess_type
from frappe.core.doctype.server_script.server_script_utils import run_server_script_api
from frappe.core.doctype.server_script.server_script_utils import get_server_script_map


ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/msword',
@@ -49,8 +49,9 @@ def execute_cmd(cmd, from_async=False):
break

# via server script
if run_server_script_api(cmd):
return None
server_script = get_server_script_map().get('_api', {}).get(cmd)
if server_script:
return run_server_script(server_script)

try:
method = get_attr(cmd)
@@ -66,7 +67,20 @@ def execute_cmd(cmd, from_async=False):

return frappe.call(method, **frappe.form_dict)


def run_server_script(server_script):
response = frappe.get_doc('Server Script', server_script).execute_method()

# some server scripts return output using flags (empty dict by default),
# while others directly modify frappe.response
# return flags if not empty dict (this overwrites frappe.response.message)
if response != {}:
return response

def is_valid_http_method(method):
if frappe.flags.in_safe_exec:
return

http_method = frappe.local.request.method

if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]:


+ 1
- 1
frappe/integrations/doctype/webhook/__init__.py Прегледај датотеку

@@ -7,7 +7,7 @@ import frappe

def run_webhooks(doc, method):
'''Run webhooks for this method'''
if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install:
if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_migrate:
return

if frappe.flags.webhooks_executed is None:


+ 10
- 5
frappe/model/db_query.py Прегледај датотеку

@@ -36,10 +36,12 @@ class DatabaseQuery(object):
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
update=None, add_total_row=None, user_settings=None, reference_doctype=None,
run=True, strict=True, pluck=None, ignore_ddl=False, parent_doctype=None) -> List:
if not ignore_permissions and \
not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype) and \
not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype):

if (
not ignore_permissions
and not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype)
and not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype)
):
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
raise frappe.PermissionError(self.doctype)

@@ -787,12 +789,15 @@ class DatabaseQuery(object):
def check_parent_permission(parent, child_doctype):
if parent:
# User may pass fake parent and get the information from the child table
if child_doctype and not frappe.db.exists('DocField',
{'parent': parent, 'options': child_doctype}):
if child_doctype and not (
frappe.db.exists('DocField', {'parent': parent, 'options': child_doctype})
or frappe.db.exists('Custom Field', {'dt': parent, 'options': child_doctype})
):
raise frappe.PermissionError

if frappe.permissions.has_permission(parent):
return

# Either parent not passed or the user doesn't have permission on parent doctype of child table!
raise frappe.PermissionError



+ 3
- 0
frappe/public/js/frappe/form/controls/datetime.js Прегледај датотеку

@@ -81,6 +81,9 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co

get_model_value() {
let value = super.get_model_value();
if (!value && !this.doc) {
value = this.last_value;
}
return frappe.datetime.get_datetime_as_string(value);
}
};

+ 19
- 16
frappe/public/scss/desk/page.scss Прегледај датотеку

@@ -156,26 +156,29 @@
.result, .no-result, .freeze {
min-height: #{"calc(100vh - 284px)"};
}
}

.msg-box {
margin-bottom: 4em;
font-size: var(--text-sm);
.msg-box {
margin-bottom: 4em;
font-size: var(--text-sm);

// To compensate for perceived centering
.null-state {
height: 85px;
width: auto;
margin-bottom: var(--margin-md);
img {
fill: var(--fg-color);
}
// To compensate for perceived centering
.null-state {
height: 85px;
width: auto;
margin-bottom: var(--margin-md);
img {
fill: var(--fg-color);
}
}
p {
font-size: var(--text-md);
}

.meta-description {
width: 45%;
margin-right: auto;
margin-left: auto;
}
.meta-description {
width: 45%;
margin-right: auto;
margin-left: auto;
}
}
}


+ 81
- 0
frappe/tests/test_fixture_import.py Прегледај датотеку

@@ -0,0 +1,81 @@
import os
import unittest
from typing import List

import frappe
from frappe.core.doctype.data_import.data_import import export_json, import_doc
from frappe.desk.form.save import savedocs
from frappe.model.delete_doc import delete_doc


class TestFixtureImport(unittest.TestCase):
def create_new_doctype(self, DocType: str) -> None:
file = frappe.get_app_path("frappe", "custom", "fixtures", f"{DocType}.json")

file = open(file, "r")
doc = file.read()
file.close()

savedocs(doc, "Save")

def insert_dummy_data_and_export(self, DocType: str, dummy_name_list: List[str]) -> str:
for name in dummy_name_list:
doc = frappe.get_doc({"doctype": DocType, "member_name": name})
doc.insert()

path_to_exported_fixtures = os.path.join(os.getcwd(), f"{DocType}_data.json")

export_json(DocType, path_to_exported_fixtures)

return path_to_exported_fixtures

def test_fixtures_import(self):
self.assertFalse(frappe.db.exists("DocType", "temp_doctype"))

self.create_new_doctype("temp_doctype")

dummy_name_list = ["jhon", "jane"]
path_to_exported_fixtures = self.insert_dummy_data_and_export("temp_doctype", dummy_name_list)
frappe.db.truncate("temp_doctype")

import_doc(path_to_exported_fixtures)

delete_doc("DocType", "temp_doctype", delete_permanently=True)
os.remove(path_to_exported_fixtures)

self.assertEqual(frappe.db.count("temp_doctype"), len(dummy_name_list))

data = frappe.get_all("temp_doctype", "member_name")
frappe.db.truncate("temp_doctype")

imported_data = set()
for item in data:
imported_data.add(item["member_name"])

self.assertEqual(set(dummy_name_list), imported_data)

def test_singles_fixtures_import(self):
self.assertFalse(frappe.db.exists("DocType", "temp_singles"))

self.create_new_doctype("temp_singles")

dummy_name_list = ["Phoebe"]
path_to_exported_fixtures = self.insert_dummy_data_and_export("temp_singles", dummy_name_list)

singles_doctype = frappe.qb.DocType("Singles")
truncate_query = (
frappe.qb.from_(singles_doctype)
.delete()
.where(singles_doctype.doctype == "temp_singles")
)
truncate_query.run()

import_doc(path_to_exported_fixtures)

delete_doc("DocType", "temp_singles", delete_permanently=True)
os.remove(path_to_exported_fixtures)

data = frappe.db.get_single_value("temp_singles", "member_name")
truncate_query.run()

self.assertEqual(data, dummy_name_list[0])

+ 24
- 1
frappe/tests/test_safe_exec.py Прегледај датотеку

@@ -31,4 +31,27 @@ class TestSafeExec(unittest.TestCase):
self.assertEqual(frappe.db.sql("SELECT Max(name) FROM tabUser"), _locals["out"])

def test_safe_query_builder(self):
self.assertRaises(frappe.PermissionError, safe_exec, '''frappe.qb.from_("User").delete().run()''')
self.assertRaises(frappe.PermissionError, safe_exec, '''frappe.qb.from_("User").delete().run()''')

def test_call(self):
# call non whitelisted method
self.assertRaises(
frappe.PermissionError,
safe_exec,
"""frappe.call("frappe.get_user")"""
)

# call whitelisted method
safe_exec("""frappe.call("ping")""")


def test_enqueue(self):
# enqueue non whitelisted method
self.assertRaises(
frappe.PermissionError,
safe_exec,
"""frappe.enqueue("frappe.get_user", now=True)"""
)

# enqueue whitelisted method
safe_exec("""frappe.enqueue("ping", now=True)""")

+ 60
- 7
frappe/utils/safe_exec.py Прегледај датотеку

@@ -14,11 +14,12 @@ import frappe.integrations.utils
import frappe.utils
import frappe.utils.data
from frappe import _
from frappe.handler import execute_cmd
from frappe.frappeclient import FrappeClient
from frappe.modules import scrub
from frappe.website.utils import get_next_link, get_shade, get_toc
from frappe.www.printview import get_visible_columns
from frappe.utils.background_jobs import enqueue, get_jobs

class ServerScriptNotEnabled(frappe.PermissionError):
pass
@@ -74,7 +75,9 @@ def get_safe_globals():

add_data_utils(datautils)

if "_" in getattr(frappe.local, 'form_dict', {}):
form_dict = getattr(frappe.local, 'form_dict', frappe._dict())

if "_" in form_dict:
del frappe.local.form_dict["_"]

user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest"
@@ -89,14 +92,16 @@ def get_safe_globals():
dict=dict,
log=frappe.log,
_dict=frappe._dict,
args=form_dict,
frappe=NamespaceDict(
call=call_whitelisted_function,
flags=frappe._dict(),
format=frappe.format_value,
format_value=frappe.format_value,
date_format=date_format,
time_format=time_format,
format_date=frappe.utils.data.global_date_format,
form_dict=getattr(frappe.local, 'form_dict', {}),
form_dict=form_dict,
bold=frappe.bold,
copy_doc=frappe.copy_doc,
errprint=frappe.errprint,
@@ -132,6 +137,7 @@ def get_safe_globals():
make_post_request=frappe.integrations.utils.make_post_request,
socketio_port=frappe.conf.socketio_port,
get_hooks=get_hooks,
enqueue=safe_enqueue,
sanitize_html=frappe.utils.sanitize_html,
log_error=frappe.log_error
),
@@ -147,7 +153,8 @@ def get_safe_globals():
guess_mimetype=mimetypes.guess_type,
html2text=html2text,
dev_server=1 if frappe._dev_server else 0,
run_script=run_script
run_script=run_script,
is_job_queued=is_job_queued,
)

add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception))
@@ -190,6 +197,55 @@ def get_safe_globals():

return out

def is_job_queued(job_name, queue="default"):
'''
:param job_name: used to identify a queued job, usually dotted path to function
:param queue: should be either long, default or short
'''

site = frappe.local.site
queued_jobs = get_jobs(site=site, queue=queue, key='job_name').get(site)
return queued_jobs and job_name in queued_jobs

def safe_enqueue(function, **kwargs):
'''
Enqueue function to be executed using a background worker
Accepts frappe.enqueue params like job_name, queue, timeout, etc.
in addition to params to be passed to function

:param function: whitelised function or API Method set in Server Script
'''

return enqueue(
'frappe.utils.safe_exec.call_whitelisted_function',
function=function,
**kwargs
)

def call_whitelisted_function(function, **kwargs):
'''Executes a whitelisted function or Server Script of type API'''

return call_with_form_dict(lambda: execute_cmd(function), kwargs)

def run_script(script, **kwargs):
'''run another server script'''

return call_with_form_dict(
lambda: frappe.get_doc('Server Script', script).execute_method(),
kwargs
)

def call_with_form_dict(function, kwargs):
# temporarily update form_dict, to use inside below call
form_dict = getattr(frappe.local, 'form_dict', frappe._dict())
if kwargs:
frappe.local.form_dict = form_dict.copy().update(kwargs)

try:
return function()
finally:
frappe.local.form_dict = form_dict

def get_python_builtins():
return {
'abs': abs,
@@ -221,9 +277,6 @@ def read_sql(query, *args, **kwargs):
raise frappe.PermissionError('Only SELECT SQL allowed in scripting')
return frappe.db.sql(query, *args, **kwargs)

def run_script(script):
'''run another server script'''
return frappe.get_doc('Server Script', script).execute_method()

def _getitem(obj, key):
# guard function for RestrictedPython


+ 2
- 2
frappe/www/404.html Прегледај датотеку

@@ -15,10 +15,10 @@
{{ _("There's nothing here") }}
</h2>
<div class="text-muted error-text">
{{ _("The page you are looking for have gone missing.") }}
{{ _("The page you are looking for has gone missing.") }}
</div>
<div class="mt-6 back-to-home"><a href='/' class='btn btn-primary'>{{ _("Back to Home") }}</a></div>
</div>
</div>

{% endblock %}
{% endblock %}

Loading…
Откажи
Сачувај