@@ -555,8 +555,13 @@ def whitelist(allow_guest=False, xss_safe=False, methods=None): | |||
def innerfn(fn): | |||
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 | |||
if allow_guest: | |||
@@ -569,6 +574,20 @@ def whitelist(allow_guest=False, xss_safe=False, methods=None): | |||
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 innfn(fn): | |||
def wrapper_fn(*args, **kwargs): | |||
@@ -118,6 +118,7 @@ class AutoRepeat(Document): | |||
def is_completed(self): | |||
return self.end_date and getdate(self.end_date) < getdate(today()) | |||
@frappe.whitelist() | |||
def get_auto_repeat_schedule(self): | |||
schedule_details = [] | |||
start_date = getdate(self.start_date) | |||
@@ -328,6 +329,7 @@ class AutoRepeat(Document): | |||
make(doctype=new_doc.doctype, name=new_doc.name, recipients=recipients, | |||
subject=subject, content=message, attachments=attachments, send_email=1) | |||
@frappe.whitelist() | |||
def fetch_linked_contacts(self): | |||
if self.reference_doctype and self.reference_document: | |||
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): | |||
return [d.as_dict(no_default_fields = True) for d in self.columns] | |||
@frappe.whitelist() | |||
def set_doctype_roles(self): | |||
if not self.get('roles') and self.is_standard == 'No': | |||
meta = frappe.get_meta(self.ref_doctype) | |||
@@ -304,7 +305,7 @@ class Report(Document): | |||
return data | |||
@Document.whitelist | |||
@frappe.whitelist() | |||
def toggle_disable(self, 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 | |||
class RolePermissionforPageandReport(Document): | |||
@frappe.whitelist() | |||
def set_report_page_data(self): | |||
self.set_custom_roles() | |||
self.check_prepared_report_disabled() | |||
@@ -35,12 +36,14 @@ class RolePermissionforPageandReport(Document): | |||
doc = frappe.get_doc(doctype, docname) | |||
return doc.roles | |||
@frappe.whitelist() | |||
def reset_roles(self): | |||
roles = self.get_standard_roles() | |||
self.set('roles', roles) | |||
self.update_custom_roles() | |||
self.update_disable_prepared_report() | |||
@frappe.whitelist() | |||
def update_report_page_data(self): | |||
self.update_custom_roles() | |||
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 `tabCustomize Form Field`") | |||
@frappe.whitelist() | |||
def fetch_to_customize(self): | |||
self.clear_existing_doc() | |||
if not self.doc_type: | |||
@@ -133,6 +134,7 @@ class CustomizeForm(Document): | |||
self.doc_type = doc_type | |||
self.name = "Customize Form" | |||
@frappe.whitelist() | |||
def save_customization(self): | |||
if not self.doc_type: | |||
return | |||
@@ -448,6 +450,7 @@ class CustomizeForm(Document): | |||
self.flags.update_db = True | |||
@frappe.whitelist() | |||
def reset_to_defaults(self): | |||
if not self.doc_type: | |||
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 | |||
class DataMigrationRun(Document): | |||
@frappe.whitelist() | |||
def run(self): | |||
self.begin() | |||
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 | |||
from frappe.utils import cstr, unique, cint | |||
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 | |||
import re | |||
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']): | |||
return [] | |||
return fn(**kwargs) | |||
return fn(**kwargs) |
@@ -29,6 +29,7 @@ class Newsletter(WebsiteGenerator): | |||
self.queue_all(test_email=True) | |||
frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id)) | |||
@frappe.whitelist() | |||
def send_emails(self): | |||
"""send emails to leads and customers""" | |||
if self.email_sent: | |||
@@ -2,17 +2,22 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
from werkzeug.wrappers import Response | |||
from six import text_type, string_types, StringIO | |||
import frappe | |||
from frappe import _ | |||
import frappe.utils | |||
import frappe.sessions | |||
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.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 werkzeug.wrappers import Response | |||
from six import string_types | |||
ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/msword', | |||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | |||
@@ -64,8 +69,9 @@ def execute_cmd(cmd, from_async=False): | |||
if from_async: | |||
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) | |||
@@ -75,31 +81,10 @@ def is_valid_http_method(method): | |||
if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]: | |||
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) | |||
def 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) | |||
def 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"), | |||
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() | |||
def uploadfile(): | |||
ret = None | |||
@@ -222,6 +198,65 @@ def get_attr(cmd): | |||
frappe.log("method:" + cmd) | |||
return method | |||
@frappe.whitelist(allow_guest = True) | |||
@frappe.whitelist(allow_guest=True) | |||
def ping(): | |||
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 | |||
import frappe | |||
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.model.base_document import BaseDocument, get_controller | |||
from frappe.model.naming import set_new_name | |||
@@ -126,10 +126,10 @@ class Document(BaseDocument): | |||
raise ValueError('Illegal arguments') | |||
@staticmethod | |||
def whitelist(f): | |||
def whitelist(fn): | |||
"""Decorator: Whitelist method to be called remotely via REST API.""" | |||
f.whitelisted = True | |||
return f | |||
frappe.whitelist()(fn) | |||
return fn | |||
def reload(self): | |||
"""Reload document from database""" | |||
@@ -1148,12 +1148,12 @@ class Document(BaseDocument): | |||
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: | |||
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): | |||
"""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; | |||
if(this.frm && this.frm.docname) { | |||
frappe.call({ | |||
method: "runserverobj", | |||
method: "run_doc_method", | |||
args: {'docs': this.frm.doc, 'method': this.df.options }, | |||
btn: this.$input, | |||
callback: function(r) { | |||
@@ -55,7 +55,7 @@ frappe.call = function(opts) { | |||
args.cmd = opts.module+'.page.'+opts.page+'.'+opts.page+'.'+opts.method; | |||
} else if(opts.doc) { | |||
$.extend(args, { | |||
cmd: "runserverobj", | |||
cmd: "run_doc_method", | |||
docs: frappe.get_doc(opts.doc.doctype, opts.doc.name), | |||
method: opts.method, | |||
args: opts.args, | |||
@@ -52,6 +52,7 @@ class EnergyPointLog(Document): | |||
reference_log.reverted = 0 | |||
reference_log.save() | |||
@frappe.whitelist() | |||
def revert(self, reason, ignore_permissions=False): | |||
if not ignore_permissions: | |||
frappe.only_for('System Manager') | |||
@@ -18,6 +18,7 @@ class BlogPost(WebsiteGenerator): | |||
order_by = "published_on desc" | |||
) | |||
@frappe.whitelist() | |||
def make_route(self): | |||
if not self.route: | |||
return frappe.db.get_value('Blog Category', self.blog_category, | |||
@@ -19,6 +19,7 @@ class PortalSettings(Document): | |||
self.append('menu', item) | |||
return True | |||
@frappe.whitelist() | |||
def reset(self): | |||
'''Restore defaults''' | |||
self.menu = [] | |||
@@ -98,6 +98,7 @@ class WebsiteTheme(Document): | |||
else: | |||
self.generate_bootstrap_theme() | |||
@frappe.whitelist() | |||
def set_as_default(self): | |||
self.generate_bootstrap_theme() | |||
self.save() | |||
@@ -106,6 +107,7 @@ class WebsiteTheme(Document): | |||
website_settings.ignore_validate = True | |||
website_settings.save() | |||
@frappe.whitelist() | |||
def get_apps(self): | |||
from frappe.utils.change_log import get_versions | |||
apps = get_versions() | |||