Ver código fonte

fix(frappe client): validate http method for frappe.client api (#11228)

version-14
Saurabh 4 anos atrás
committed by GitHub
pai
commit
a0086db9b6
Nenhuma chave conhecida encontrada para esta assinatura no banco de dados ID da chave GPG: 4AEE18F83AFDEB23
4 arquivos alterados com 65 adições e 18 exclusões
  1. +11
    -2
      frappe/__init__.py
  2. +12
    -12
      frappe/client.py
  3. +8
    -4
      frappe/handler.py
  4. +34
    -0
      frappe/tests/test_client.py

+ 11
- 2
frappe/__init__.py Ver arquivo

@@ -514,12 +514,15 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
whitelisted = [] whitelisted = []
guest_methods = [] guest_methods = []
xss_safe_methods = [] xss_safe_methods = []
def whitelist(allow_guest=False, xss_safe=False):
allowed_http_methods_for_whitelisted_func = {}

def whitelist(allow_guest=False, xss_safe=False, methods=None):
""" """
Decorator for whitelisting a function and making it accessible via HTTP. Decorator for whitelisting a function and making it accessible via HTTP.
Standard request will be `/api/method/[path.to.method]` Standard request will be `/api/method/[path.to.method]`


:param allow_guest: Allow non logged-in user to access this method. :param allow_guest: Allow non logged-in user to access this method.
:param methods: Allowed http method to access the method.


Use as: Use as:


@@ -527,10 +530,16 @@ def whitelist(allow_guest=False, xss_safe=False):
def myfunc(param1, param2): def myfunc(param1, param2):
pass pass
""" """

if not methods:
methods = ['GET', 'POST', 'PUT', 'DELETE']

def innerfn(fn): def innerfn(fn):
global whitelisted, guest_methods, xss_safe_methods
global whitelisted, guest_methods, xss_safe_methods, allowed_http_methods_for_whitelisted_func
whitelisted.append(fn) whitelisted.append(fn)


allowed_http_methods_for_whitelisted_func[fn] = methods

if allow_guest: if allow_guest:
guest_methods.append(fn) guest_methods.append(fn)




+ 12
- 12
frappe/client.py Ver arquivo

@@ -107,7 +107,7 @@ def get_single_value(doctype, field):
value = frappe.db.get_single_value(doctype, field) value = frappe.db.get_single_value(doctype, field)
return value return value


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def set_value(doctype, name, fieldname, value=None): def set_value(doctype, name, fieldname, value=None):
'''Set a value using get_doc, group of values '''Set a value using get_doc, group of values


@@ -142,7 +142,7 @@ def set_value(doctype, name, fieldname, value=None):


return doc.as_dict() return doc.as_dict()


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def insert(doc=None): def insert(doc=None):
'''Insert a document '''Insert a document


@@ -160,7 +160,7 @@ def insert(doc=None):
doc = frappe.get_doc(doc).insert() doc = frappe.get_doc(doc).insert()
return doc.as_dict() return doc.as_dict()


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def insert_many(docs=None): def insert_many(docs=None):
'''Insert multiple documents '''Insert multiple documents


@@ -186,7 +186,7 @@ def insert_many(docs=None):


return out return out


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def save(doc): def save(doc):
'''Update (save) an existing document '''Update (save) an existing document


@@ -199,7 +199,7 @@ def save(doc):


return doc.as_dict() return doc.as_dict()


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def rename_doc(doctype, old_name, new_name, merge=False): def rename_doc(doctype, old_name, new_name, merge=False):
'''Rename document '''Rename document


@@ -209,7 +209,7 @@ def rename_doc(doctype, old_name, new_name, merge=False):
new_name = frappe.rename_doc(doctype, old_name, new_name, merge=merge) new_name = frappe.rename_doc(doctype, old_name, new_name, merge=merge)
return new_name return new_name


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def submit(doc): def submit(doc):
'''Submit a document '''Submit a document


@@ -222,7 +222,7 @@ def submit(doc):


return doc.as_dict() return doc.as_dict()


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def cancel(doctype, name): def cancel(doctype, name):
'''Cancel a document '''Cancel a document


@@ -233,7 +233,7 @@ def cancel(doctype, name):


return wrapper.as_dict() return wrapper.as_dict()


@frappe.whitelist()
@frappe.whitelist(methods=['DELETE', 'POST'])
def delete(doctype, name): def delete(doctype, name):
'''Delete a remote document '''Delete a remote document


@@ -241,13 +241,13 @@ def delete(doctype, name):
:param name: name of the document to be deleted''' :param name: name of the document to be deleted'''
frappe.delete_doc(doctype, name, ignore_missing=False) frappe.delete_doc(doctype, name, ignore_missing=False)


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def set_default(key, value, parent=None): def set_default(key, value, parent=None):
"""set a user default value""" """set a user default value"""
frappe.db.set_default(key, value, parent or frappe.session.user) frappe.db.set_default(key, value, parent or frappe.session.user)
frappe.clear_cache(user=frappe.session.user) frappe.clear_cache(user=frappe.session.user)


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def make_width_property_setter(doc): def make_width_property_setter(doc):
'''Set width Property Setter '''Set width Property Setter


@@ -257,7 +257,7 @@ def make_width_property_setter(doc):
if doc["doctype"]=="Property Setter" and doc["property"]=="width": if doc["doctype"]=="Property Setter" and doc["property"]=="width":
frappe.get_doc(doc).insert(ignore_permissions = True) frappe.get_doc(doc).insert(ignore_permissions = True)


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def bulk_update(docs): def bulk_update(docs):
'''Bulk update documents '''Bulk update documents


@@ -333,7 +333,7 @@ def get_time_zone():
'''Returns default time zone''' '''Returns default time zone'''
return {"time_zone": frappe.defaults.get_defaults().get("time_zone")} return {"time_zone": frappe.defaults.get_defaults().get("time_zone")}


@frappe.whitelist()
@frappe.whitelist(methods=['POST', 'PUT'])
def attach_file(filename=None, filedata=None, doctype=None, docname=None, folder=None, decode_base64=False, is_private=None, docfield=None): def attach_file(filename=None, filedata=None, doctype=None, docname=None, folder=None, decode_base64=False, is_private=None, docfield=None):
'''Attach a file to Document (POST) '''Attach a file to Document (POST)




+ 8
- 4
frappe/handler.py Ver arquivo

@@ -65,16 +65,21 @@ def execute_cmd(cmd, from_async=False):
method = method.queue method = method.queue


is_whitelisted(method) is_whitelisted(method)
is_valid_http_method(method)


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


def is_valid_http_method(method):
http_method = frappe.local.request.method

if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]:
frappe.throw(_("Not permitted"), frappe.PermissionError)


def is_whitelisted(method): def is_whitelisted(method):
# check if whitelisted # check if whitelisted
if frappe.session['user'] == 'Guest': if frappe.session['user'] == 'Guest':
if (method not in frappe.guest_methods): if (method not in frappe.guest_methods):
frappe.msgprint(_("Not permitted"))
raise frappe.PermissionError('Not Allowed, {0}'.format(method))
frappe.throw(_("Not permitted"), frappe.PermissionError)


if method not in frappe.xss_safe_methods: if method not in frappe.xss_safe_methods:
# strictly sanitize form_dict # strictly sanitize form_dict
@@ -85,8 +90,7 @@ def is_whitelisted(method):


else: else:
if not method in frappe.whitelisted: if not method in frappe.whitelisted:
frappe.msgprint(_("Not permitted"))
raise frappe.PermissionError('Not Allowed, {0}'.format(method))
frappe.throw(_("Not permitted"), frappe.PermissionError)


@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def version(): def version():


+ 34
- 0
frappe/tests/test_client.py Ver arquivo

@@ -21,3 +21,37 @@ class TestClient(unittest.TestCase):


self.assertFalse(frappe.db.exists("ToDo", todo.name)) self.assertFalse(frappe.db.exists("ToDo", todo.name))
self.assertRaises(frappe.DoesNotExistError, delete, "ToDo", todo.name) self.assertRaises(frappe.DoesNotExistError, delete, "ToDo", todo.name)

def test_http_valid_method_access(self):
from frappe.client import delete
from frappe.handler import execute_cmd

frappe.set_user("Administrator")

frappe.local.request = frappe._dict()
frappe.local.request.method = 'POST'

frappe.local.form_dict = frappe._dict({
'doc': dict(doctype='ToDo', description='Valid http method'),
'cmd': 'frappe.client.save'
})
todo = execute_cmd('frappe.client.save')

self.assertEqual(todo.get('description'), 'Valid http method')

delete("ToDo", todo.name)

def test_http_invalid_method_access(self):
from frappe.handler import execute_cmd

frappe.set_user("Administrator")

frappe.local.request = frappe._dict()
frappe.local.request.method = 'GET'

frappe.local.form_dict = frappe._dict({
'doc': dict(doctype='ToDo', description='Invalid http method'),
'cmd': 'frappe.client.save'
})

self.assertRaises(frappe.PermissionError, execute_cmd, 'frappe.client.save')

Carregando…
Cancelar
Salvar