@@ -555,8 +555,13 @@ def whitelist(allow_guest=False, xss_safe=False, methods=None): | |||||
def innerfn(fn): | def innerfn(fn): | ||||
global whitelisted, guest_methods, xss_safe_methods, allowed_http_methods_for_whitelisted_func | global whitelisted, guest_methods, xss_safe_methods, allowed_http_methods_for_whitelisted_func | ||||
whitelisted.append(fn) | |||||
# get function from the unbound / bound method | |||||
# this is needed because functions can be compared, but not methods | |||||
if hasattr(fn, '__func__'): | |||||
fn = fn.__func__ | |||||
whitelisted.append(fn) | |||||
allowed_http_methods_for_whitelisted_func[fn] = methods | allowed_http_methods_for_whitelisted_func[fn] = methods | ||||
if allow_guest: | if allow_guest: | ||||
@@ -569,6 +574,20 @@ def whitelist(allow_guest=False, xss_safe=False, methods=None): | |||||
return innerfn | return innerfn | ||||
def is_whitelisted(method): | |||||
from frappe.utils import sanitize_html | |||||
is_guest = session['user'] == 'Guest' | |||||
if method not in whitelisted or is_guest and method not in guest_methods: | |||||
throw(_("Not permitted"), PermissionError) | |||||
if is_guest and method not in xss_safe_methods: | |||||
# strictly sanitize form_dict | |||||
# escapes html characters like <> except for predefined tags like a, b, ul etc. | |||||
for key, value in form_dict.items(): | |||||
if isinstance(value, string_types): | |||||
form_dict[key] = sanitize_html(value) | |||||
def read_only(): | def read_only(): | ||||
def innfn(fn): | def innfn(fn): | ||||
def wrapper_fn(*args, **kwargs): | def wrapper_fn(*args, **kwargs): | ||||
@@ -118,6 +118,7 @@ class AutoRepeat(Document): | |||||
def is_completed(self): | def is_completed(self): | ||||
return self.end_date and getdate(self.end_date) < getdate(today()) | return self.end_date and getdate(self.end_date) < getdate(today()) | ||||
@frappe.whitelist() | |||||
def get_auto_repeat_schedule(self): | def get_auto_repeat_schedule(self): | ||||
schedule_details = [] | schedule_details = [] | ||||
start_date = getdate(self.start_date) | start_date = getdate(self.start_date) | ||||
@@ -328,6 +329,7 @@ class AutoRepeat(Document): | |||||
make(doctype=new_doc.doctype, name=new_doc.name, recipients=recipients, | make(doctype=new_doc.doctype, name=new_doc.name, recipients=recipients, | ||||
subject=subject, content=message, attachments=attachments, send_email=1) | subject=subject, content=message, attachments=attachments, send_email=1) | ||||
@frappe.whitelist() | |||||
def fetch_linked_contacts(self): | def fetch_linked_contacts(self): | ||||
if self.reference_doctype and self.reference_document: | if self.reference_doctype and self.reference_document: | ||||
res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) | res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) | ||||
@@ -58,6 +58,7 @@ class Report(Document): | |||||
def get_columns(self): | def get_columns(self): | ||||
return [d.as_dict(no_default_fields = True) for d in self.columns] | return [d.as_dict(no_default_fields = True) for d in self.columns] | ||||
@frappe.whitelist() | |||||
def set_doctype_roles(self): | def set_doctype_roles(self): | ||||
if not self.get('roles') and self.is_standard == 'No': | if not self.get('roles') and self.is_standard == 'No': | ||||
meta = frappe.get_meta(self.ref_doctype) | meta = frappe.get_meta(self.ref_doctype) | ||||
@@ -304,7 +305,7 @@ class Report(Document): | |||||
return data | return data | ||||
@Document.whitelist | |||||
@frappe.whitelist() | |||||
def toggle_disable(self, disable): | def toggle_disable(self, disable): | ||||
self.db_set("disabled", cint(disable)) | self.db_set("disabled", cint(disable)) | ||||
@@ -8,6 +8,7 @@ from frappe.core.doctype.report.report import is_prepared_report_disabled | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class RolePermissionforPageandReport(Document): | class RolePermissionforPageandReport(Document): | ||||
@frappe.whitelist() | |||||
def set_report_page_data(self): | def set_report_page_data(self): | ||||
self.set_custom_roles() | self.set_custom_roles() | ||||
self.check_prepared_report_disabled() | self.check_prepared_report_disabled() | ||||
@@ -35,12 +36,14 @@ class RolePermissionforPageandReport(Document): | |||||
doc = frappe.get_doc(doctype, docname) | doc = frappe.get_doc(doctype, docname) | ||||
return doc.roles | return doc.roles | ||||
@frappe.whitelist() | |||||
def reset_roles(self): | def reset_roles(self): | ||||
roles = self.get_standard_roles() | roles = self.get_standard_roles() | ||||
self.set('roles', roles) | self.set('roles', roles) | ||||
self.update_custom_roles() | self.update_custom_roles() | ||||
self.update_disable_prepared_report() | self.update_disable_prepared_report() | ||||
@frappe.whitelist() | |||||
def update_report_page_data(self): | def update_report_page_data(self): | ||||
self.update_custom_roles() | self.update_custom_roles() | ||||
self.update_disable_prepared_report() | self.update_disable_prepared_report() | ||||
@@ -24,6 +24,7 @@ class CustomizeForm(Document): | |||||
frappe.db.sql("delete from tabSingles where doctype='Customize Form'") | frappe.db.sql("delete from tabSingles where doctype='Customize Form'") | ||||
frappe.db.sql("delete from `tabCustomize Form Field`") | frappe.db.sql("delete from `tabCustomize Form Field`") | ||||
@frappe.whitelist() | |||||
def fetch_to_customize(self): | def fetch_to_customize(self): | ||||
self.clear_existing_doc() | self.clear_existing_doc() | ||||
if not self.doc_type: | if not self.doc_type: | ||||
@@ -133,6 +134,7 @@ class CustomizeForm(Document): | |||||
self.doc_type = doc_type | self.doc_type = doc_type | ||||
self.name = "Customize Form" | self.name = "Customize Form" | ||||
@frappe.whitelist() | |||||
def save_customization(self): | def save_customization(self): | ||||
if not self.doc_type: | if not self.doc_type: | ||||
return | return | ||||
@@ -448,6 +450,7 @@ class CustomizeForm(Document): | |||||
self.flags.update_db = True | self.flags.update_db = True | ||||
@frappe.whitelist() | |||||
def reset_to_defaults(self): | def reset_to_defaults(self): | ||||
if not self.doc_type: | if not self.doc_type: | ||||
return | return | ||||
@@ -10,6 +10,7 @@ from frappe.utils import cstr | |||||
from frappe.data_migration.doctype.data_migration_mapping.data_migration_mapping import get_source_value | from frappe.data_migration.doctype.data_migration_mapping.data_migration_mapping import get_source_value | ||||
class DataMigrationRun(Document): | class DataMigrationRun(Document): | ||||
@frappe.whitelist() | |||||
def run(self): | def run(self): | ||||
self.begin() | self.begin() | ||||
if self.total_pages > 0: | if self.total_pages > 0: | ||||
@@ -1,81 +0,0 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals | |||||
import json, inspect | |||||
import frappe | |||||
from frappe import _ | |||||
from frappe.utils import cint | |||||
from six import text_type, string_types | |||||
@frappe.whitelist() | |||||
def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None): | |||||
"""run controller method - old style""" | |||||
if not args: args = arg or "" | |||||
if dt: # not called from a doctype (from a page) | |||||
if not dn: dn = dt # single | |||||
doc = frappe.get_doc(dt, dn) | |||||
else: | |||||
doc = frappe.get_doc(json.loads(docs)) | |||||
doc._original_modified = doc.modified | |||||
doc.check_if_latest() | |||||
if not doc.has_permission("read"): | |||||
frappe.msgprint(_("Not permitted"), raise_exception = True) | |||||
if doc: | |||||
try: | |||||
args = json.loads(args) | |||||
except ValueError: | |||||
args = args | |||||
try: | |||||
fnargs, varargs, varkw, defaults = inspect.getargspec(getattr(doc, method)) | |||||
except ValueError: | |||||
fnargs = inspect.getfullargspec(getattr(doc, method)).args | |||||
varargs = inspect.getfullargspec(getattr(doc, method)).varargs | |||||
varkw = inspect.getfullargspec(getattr(doc, method)).varkw | |||||
defaults = inspect.getfullargspec(getattr(doc, method)).defaults | |||||
if not fnargs or (len(fnargs)==1 and fnargs[0]=="self"): | |||||
r = doc.run_method(method) | |||||
elif "args" in fnargs or not isinstance(args, dict): | |||||
r = doc.run_method(method, args) | |||||
else: | |||||
r = doc.run_method(method, **args) | |||||
if r: | |||||
#build output as csv | |||||
if cint(frappe.form_dict.get('as_csv')): | |||||
make_csv_output(r, doc.doctype) | |||||
else: | |||||
frappe.response['message'] = r | |||||
frappe.response.docs.append(doc) | |||||
def make_csv_output(res, dt): | |||||
"""send method response as downloadable CSV file""" | |||||
import frappe | |||||
from six import StringIO | |||||
import csv | |||||
f = StringIO() | |||||
writer = csv.writer(f) | |||||
for r in res: | |||||
row = [] | |||||
for v in r: | |||||
if isinstance(v, string_types): | |||||
v = v.encode("utf-8") | |||||
row.append(v) | |||||
writer.writerow(row) | |||||
f.seek(0) | |||||
frappe.response['result'] = text_type(f.read(), 'utf-8') | |||||
frappe.response['type'] = 'csv' | |||||
frappe.response['doctype'] = dt.replace(' ','') |
@@ -6,8 +6,7 @@ from __future__ import unicode_literals | |||||
import frappe, json | import frappe, json | ||||
from frappe.utils import cstr, unique, cint | from frappe.utils import cstr, unique, cint | ||||
from frappe.permissions import has_permission | from frappe.permissions import has_permission | ||||
from frappe.handler import is_whitelisted | |||||
from frappe import _ | |||||
from frappe import _, is_whitelisted | |||||
from six import string_types | from six import string_types | ||||
import re | import re | ||||
import wrapt | import wrapt | ||||
@@ -221,4 +220,4 @@ def validate_and_sanitize_search_inputs(fn, instance, args, kwargs): | |||||
if kwargs['doctype'] and not frappe.db.exists('DocType', kwargs['doctype']): | if kwargs['doctype'] and not frappe.db.exists('DocType', kwargs['doctype']): | ||||
return [] | return [] | ||||
return fn(**kwargs) | |||||
return fn(**kwargs) |
@@ -29,6 +29,7 @@ class Newsletter(WebsiteGenerator): | |||||
self.queue_all(test_email=True) | self.queue_all(test_email=True) | ||||
frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id)) | frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id)) | ||||
@frappe.whitelist() | |||||
def send_emails(self): | def send_emails(self): | ||||
"""send emails to leads and customers""" | """send emails to leads and customers""" | ||||
if self.email_sent: | if self.email_sent: | ||||
@@ -2,17 +2,22 @@ | |||||
# MIT License. See license.txt | # MIT License. See license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from werkzeug.wrappers import Response | |||||
from six import text_type, string_types, StringIO | |||||
import frappe | import frappe | ||||
from frappe import _ | |||||
import frappe.utils | import frappe.utils | ||||
import frappe.sessions | import frappe.sessions | ||||
import frappe.desk.form.run_method | import frappe.desk.form.run_method | ||||
from frappe.utils.response import build_response | |||||
from frappe.api import validate_auth | |||||
from frappe.utils import cint | from frappe.utils import cint | ||||
from frappe.api import validate_auth | |||||
from frappe import _, is_whitelisted | |||||
from frappe.utils.response import build_response | |||||
from frappe.utils.csvutils import build_csv_response | |||||
from frappe.core.doctype.server_script.server_script_utils import run_server_script_api | from frappe.core.doctype.server_script.server_script_utils import run_server_script_api | ||||
from werkzeug.wrappers import Response | |||||
from six import string_types | |||||
ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/msword', | ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/msword', | ||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | ||||
@@ -64,8 +69,9 @@ def execute_cmd(cmd, from_async=False): | |||||
if from_async: | if from_async: | ||||
method = method.queue | method = method.queue | ||||
is_whitelisted(method) | |||||
is_valid_http_method(method) | |||||
if method != run_doc_method: | |||||
is_whitelisted(method) | |||||
is_valid_http_method(method) | |||||
return frappe.call(method, **frappe.form_dict) | return frappe.call(method, **frappe.form_dict) | ||||
@@ -75,31 +81,10 @@ def is_valid_http_method(method): | |||||
if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]: | if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]: | ||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | frappe.throw(_("Not permitted"), frappe.PermissionError) | ||||
def is_whitelisted(method): | |||||
# check if whitelisted | |||||
if frappe.session['user'] == 'Guest': | |||||
if (method not in frappe.guest_methods): | |||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | |||||
if method not in frappe.xss_safe_methods: | |||||
# strictly sanitize form_dict | |||||
# escapes html characters like <> except for predefined tags like a, b, ul etc. | |||||
for key, value in frappe.form_dict.items(): | |||||
if isinstance(value, string_types): | |||||
frappe.form_dict[key] = frappe.utils.sanitize_html(value) | |||||
else: | |||||
if not method in frappe.whitelisted: | |||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | |||||
@frappe.whitelist(allow_guest=True) | @frappe.whitelist(allow_guest=True) | ||||
def version(): | def version(): | ||||
return frappe.__version__ | return frappe.__version__ | ||||
@frappe.whitelist() | |||||
def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None): | |||||
frappe.desk.form.run_method.runserverobj(method, docs=docs, dt=dt, dn=dn, arg=arg, args=args) | |||||
@frappe.whitelist(allow_guest=True) | @frappe.whitelist(allow_guest=True) | ||||
def logout(): | def logout(): | ||||
frappe.local.login_manager.logout() | frappe.local.login_manager.logout() | ||||
@@ -112,15 +97,6 @@ def web_logout(): | |||||
frappe.respond_as_web_page(_("Logged Out"), _("You have been successfully logged out"), | frappe.respond_as_web_page(_("Logged Out"), _("You have been successfully logged out"), | ||||
indicator_color='green') | indicator_color='green') | ||||
@frappe.whitelist(allow_guest=True) | |||||
def run_custom_method(doctype, name, custom_method): | |||||
"""cmd=run_custom_method&doctype={doctype}&name={name}&custom_method={custom_method}""" | |||||
doc = frappe.get_doc(doctype, name) | |||||
if getattr(doc, custom_method, frappe._dict()).is_whitelisted: | |||||
frappe.call(getattr(doc, custom_method), **frappe.local.form_dict) | |||||
else: | |||||
frappe.throw(_("Not permitted"), frappe.PermissionError) | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def uploadfile(): | def uploadfile(): | ||||
ret = None | ret = None | ||||
@@ -222,6 +198,65 @@ def get_attr(cmd): | |||||
frappe.log("method:" + cmd) | frappe.log("method:" + cmd) | ||||
return method | return method | ||||
@frappe.whitelist(allow_guest = True) | |||||
@frappe.whitelist(allow_guest=True) | |||||
def ping(): | def ping(): | ||||
return "pong" | return "pong" | ||||
@frappe.whitelist() | |||||
def run_doc_method(method, docs=None, dt=None, dn=None, arg=None, args=None): | |||||
"""run controller method - old style""" | |||||
import json, inspect | |||||
if not args: args = arg or "" | |||||
if dt: # not called from a doctype (from a page) | |||||
if not dn: dn = dt # single | |||||
doc = frappe.get_doc(dt, dn) | |||||
else: | |||||
doc = frappe.get_doc(json.loads(docs)) | |||||
doc._original_modified = doc.modified | |||||
doc.check_if_latest() | |||||
if not doc.has_permission("read"): | |||||
frappe.msgprint(_("Not permitted"), raise_exception = True) | |||||
if not doc: | |||||
return | |||||
try: | |||||
args = json.loads(args) | |||||
except ValueError: | |||||
args = args | |||||
method_obj = getattr(doc, method) | |||||
is_whitelisted(getattr(method_obj, '__func__', method_obj)) | |||||
try: | |||||
fnargs = inspect.getargspec(method_obj)[0] | |||||
except ValueError: | |||||
fnargs = inspect.getfullargspec(method_obj).args | |||||
if not fnargs or (len(fnargs)==1 and fnargs[0]=="self"): | |||||
r = doc.run_method(method) | |||||
elif "args" in fnargs or not isinstance(args, dict): | |||||
r = doc.run_method(method, args) | |||||
else: | |||||
r = doc.run_method(method, **args) | |||||
frappe.response.docs.append(doc) | |||||
if not r: | |||||
return | |||||
# build output as csv | |||||
if cint(frappe.form_dict.get('as_csv')): | |||||
build_csv_response(r, doc.doctype.replace(' ', '')) | |||||
return | |||||
frappe.response['message'] = r | |||||
# for backwards compatibility | |||||
runserverobj = run_doc_method |
@@ -4,7 +4,7 @@ | |||||
from __future__ import unicode_literals, print_function | from __future__ import unicode_literals, print_function | ||||
import frappe | import frappe | ||||
import time | import time | ||||
from frappe import _, msgprint | |||||
from frappe import _, msgprint, is_whitelisted | |||||
from frappe.utils import flt, cstr, now, get_datetime_str, file_lock, date_diff | from frappe.utils import flt, cstr, now, get_datetime_str, file_lock, date_diff | ||||
from frappe.model.base_document import BaseDocument, get_controller | from frappe.model.base_document import BaseDocument, get_controller | ||||
from frappe.model.naming import set_new_name | from frappe.model.naming import set_new_name | ||||
@@ -126,10 +126,10 @@ class Document(BaseDocument): | |||||
raise ValueError('Illegal arguments') | raise ValueError('Illegal arguments') | ||||
@staticmethod | @staticmethod | ||||
def whitelist(f): | |||||
def whitelist(fn): | |||||
"""Decorator: Whitelist method to be called remotely via REST API.""" | """Decorator: Whitelist method to be called remotely via REST API.""" | ||||
f.whitelisted = True | |||||
return f | |||||
frappe.whitelist()(fn) | |||||
return fn | |||||
def reload(self): | def reload(self): | ||||
"""Reload document from database""" | """Reload document from database""" | ||||
@@ -1148,12 +1148,12 @@ class Document(BaseDocument): | |||||
return composer | return composer | ||||
def is_whitelisted(self, method): | |||||
fn = getattr(self, method, None) | |||||
def is_whitelisted(self, method_name): | |||||
method = getattr(self, method_name, None) | |||||
if not fn: | if not fn: | ||||
raise NotFound("Method {0} not found".format(method)) | |||||
elif not getattr(fn, "whitelisted", False): | |||||
raise Forbidden("Method {0} not whitelisted".format(method)) | |||||
raise NotFound("Method {0} not found".format(method_name)) | |||||
is_whitelisted(getattr(method, '__func__', method)) | |||||
def validate_value(self, fieldname, condition, val2, doc=None, raise_exception=None): | def validate_value(self, fieldname, condition, val2, doc=None, raise_exception=None): | ||||
"""Check that value of fieldname should be 'condition' val2 | """Check that value of fieldname should be 'condition' val2 | ||||
@@ -34,7 +34,7 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ | |||||
var me = this; | var me = this; | ||||
if(this.frm && this.frm.docname) { | if(this.frm && this.frm.docname) { | ||||
frappe.call({ | frappe.call({ | ||||
method: "runserverobj", | |||||
method: "run_doc_method", | |||||
args: {'docs': this.frm.doc, 'method': this.df.options }, | args: {'docs': this.frm.doc, 'method': this.df.options }, | ||||
btn: this.$input, | btn: this.$input, | ||||
callback: function(r) { | callback: function(r) { | ||||
@@ -55,7 +55,7 @@ frappe.call = function(opts) { | |||||
args.cmd = opts.module+'.page.'+opts.page+'.'+opts.page+'.'+opts.method; | args.cmd = opts.module+'.page.'+opts.page+'.'+opts.page+'.'+opts.method; | ||||
} else if(opts.doc) { | } else if(opts.doc) { | ||||
$.extend(args, { | $.extend(args, { | ||||
cmd: "runserverobj", | |||||
cmd: "run_doc_method", | |||||
docs: frappe.get_doc(opts.doc.doctype, opts.doc.name), | docs: frappe.get_doc(opts.doc.doctype, opts.doc.name), | ||||
method: opts.method, | method: opts.method, | ||||
args: opts.args, | args: opts.args, | ||||
@@ -52,6 +52,7 @@ class EnergyPointLog(Document): | |||||
reference_log.reverted = 0 | reference_log.reverted = 0 | ||||
reference_log.save() | reference_log.save() | ||||
@frappe.whitelist() | |||||
def revert(self, reason, ignore_permissions=False): | def revert(self, reason, ignore_permissions=False): | ||||
if not ignore_permissions: | if not ignore_permissions: | ||||
frappe.only_for('System Manager') | frappe.only_for('System Manager') | ||||
@@ -18,6 +18,7 @@ class BlogPost(WebsiteGenerator): | |||||
order_by = "published_on desc" | order_by = "published_on desc" | ||||
) | ) | ||||
@frappe.whitelist() | |||||
def make_route(self): | def make_route(self): | ||||
if not self.route: | if not self.route: | ||||
return frappe.db.get_value('Blog Category', self.blog_category, | return frappe.db.get_value('Blog Category', self.blog_category, | ||||
@@ -19,6 +19,7 @@ class PortalSettings(Document): | |||||
self.append('menu', item) | self.append('menu', item) | ||||
return True | return True | ||||
@frappe.whitelist() | |||||
def reset(self): | def reset(self): | ||||
'''Restore defaults''' | '''Restore defaults''' | ||||
self.menu = [] | self.menu = [] | ||||
@@ -98,6 +98,7 @@ class WebsiteTheme(Document): | |||||
else: | else: | ||||
self.generate_bootstrap_theme() | self.generate_bootstrap_theme() | ||||
@frappe.whitelist() | |||||
def set_as_default(self): | def set_as_default(self): | ||||
self.generate_bootstrap_theme() | self.generate_bootstrap_theme() | ||||
self.save() | self.save() | ||||
@@ -106,6 +107,7 @@ class WebsiteTheme(Document): | |||||
website_settings.ignore_validate = True | website_settings.ignore_validate = True | ||||
website_settings.save() | website_settings.save() | ||||
@frappe.whitelist() | |||||
def get_apps(self): | def get_apps(self): | ||||
from frappe.utils.change_log import get_versions | from frappe.utils.change_log import get_versions | ||||
apps = get_versions() | apps = get_versions() | ||||