Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 
 

456 rindas
11 KiB

  1. import copy
  2. import inspect
  3. import json
  4. import mimetypes
  5. import RestrictedPython.Guards
  6. from html2text import html2text
  7. from RestrictedPython import compile_restricted, safe_globals
  8. import frappe
  9. import frappe.exceptions
  10. import frappe.integrations.utils
  11. import frappe.utils
  12. import frappe.utils.data
  13. from frappe import _
  14. from frappe.handler import execute_cmd
  15. from frappe.frappeclient import FrappeClient
  16. from frappe.modules import scrub
  17. from frappe.website.utils import get_next_link, get_shade, get_toc
  18. from frappe.www.printview import get_visible_columns
  19. from frappe.utils.background_jobs import enqueue, get_jobs
  20. class ServerScriptNotEnabled(frappe.PermissionError):
  21. pass
  22. class NamespaceDict(frappe._dict):
  23. """Raise AttributeError if function not found in namespace"""
  24. def __getattr__(self, key):
  25. ret = self.get(key)
  26. if (not ret and key.startswith("__")) or (key not in self):
  27. def default_function(*args, **kwargs):
  28. raise AttributeError(f"module has no attribute '{key}'")
  29. return default_function
  30. return ret
  31. def safe_exec(script, _globals=None, _locals=None, restrict_commit_rollback=False):
  32. # server scripts can be disabled via site_config.json
  33. # they are enabled by default
  34. if 'server_script_enabled' in frappe.conf:
  35. enabled = frappe.conf.server_script_enabled
  36. else:
  37. enabled = True
  38. if not enabled:
  39. frappe.throw(_('Please Enable Server Scripts'), ServerScriptNotEnabled)
  40. # build globals
  41. exec_globals = get_safe_globals()
  42. if _globals:
  43. exec_globals.update(_globals)
  44. if restrict_commit_rollback:
  45. exec_globals.frappe.db.pop('commit', None)
  46. exec_globals.frappe.db.pop('rollback', None)
  47. # execute script compiled by RestrictedPython
  48. frappe.flags.in_safe_exec = True
  49. exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used
  50. frappe.flags.in_safe_exec = False
  51. return exec_globals, _locals
  52. def get_safe_globals():
  53. datautils = frappe._dict()
  54. if frappe.db:
  55. date_format = frappe.db.get_default("date_format") or "yyyy-mm-dd"
  56. time_format = frappe.db.get_default("time_format") or "HH:mm:ss"
  57. else:
  58. date_format = "yyyy-mm-dd"
  59. time_format = "HH:mm:ss"
  60. add_data_utils(datautils)
  61. form_dict = getattr(frappe.local, 'form_dict', frappe._dict())
  62. if "_" in form_dict:
  63. del frappe.local.form_dict["_"]
  64. user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest"
  65. out = NamespaceDict(
  66. # make available limited methods of frappe
  67. json=NamespaceDict(
  68. loads=json.loads,
  69. dumps=json.dumps
  70. ),
  71. as_json=frappe.as_json,
  72. dict=dict,
  73. log=frappe.log,
  74. _dict=frappe._dict,
  75. args=form_dict,
  76. frappe=NamespaceDict(
  77. call=call_whitelisted_function,
  78. flags=frappe._dict(),
  79. format=frappe.format_value,
  80. format_value=frappe.format_value,
  81. date_format=date_format,
  82. time_format=time_format,
  83. format_date=frappe.utils.data.global_date_format,
  84. form_dict=form_dict,
  85. bold=frappe.bold,
  86. copy_doc=frappe.copy_doc,
  87. errprint=frappe.errprint,
  88. qb=frappe.qb,
  89. get_meta=frappe.get_meta,
  90. get_doc=frappe.get_doc,
  91. get_cached_doc=frappe.get_cached_doc,
  92. get_list=frappe.get_list,
  93. get_all=frappe.get_all,
  94. get_system_settings=frappe.get_system_settings,
  95. rename_doc=frappe.rename_doc,
  96. utils=datautils,
  97. get_url=frappe.utils.get_url,
  98. render_template=frappe.render_template,
  99. msgprint=frappe.msgprint,
  100. throw=frappe.throw,
  101. sendmail=frappe.sendmail,
  102. get_print=frappe.get_print,
  103. attach_print=frappe.attach_print,
  104. user=user,
  105. get_fullname=frappe.utils.get_fullname,
  106. get_gravatar=frappe.utils.get_gravatar_url,
  107. full_name=frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest",
  108. request=getattr(frappe.local, 'request', {}),
  109. session=frappe._dict(
  110. user=user,
  111. csrf_token=frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else ''
  112. ),
  113. make_get_request=frappe.integrations.utils.make_get_request,
  114. make_post_request=frappe.integrations.utils.make_post_request,
  115. socketio_port=frappe.conf.socketio_port,
  116. get_hooks=get_hooks,
  117. enqueue=safe_enqueue,
  118. sanitize_html=frappe.utils.sanitize_html,
  119. log_error=frappe.log_error,
  120. db = NamespaceDict(
  121. get_list=frappe.get_list,
  122. get_all=frappe.get_all,
  123. get_value=frappe.db.get_value,
  124. set_value=frappe.db.set_value,
  125. get_single_value=frappe.db.get_single_value,
  126. get_default=frappe.db.get_default,
  127. exists=frappe.db.exists,
  128. count=frappe.db.count,
  129. escape=frappe.db.escape,
  130. sql=read_sql,
  131. commit=frappe.db.commit,
  132. rollback=frappe.db.rollback,
  133. ),
  134. ),
  135. FrappeClient=FrappeClient,
  136. style=frappe._dict(
  137. border_color='#d1d8dd'
  138. ),
  139. get_toc=get_toc,
  140. get_next_link=get_next_link,
  141. _=frappe._,
  142. get_shade=get_shade,
  143. scrub=scrub,
  144. guess_mimetype=mimetypes.guess_type,
  145. html2text=html2text,
  146. dev_server=1 if frappe._dev_server else 0,
  147. run_script=run_script,
  148. is_job_queued=is_job_queued,
  149. get_visible_columns=get_visible_columns,
  150. )
  151. add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception))
  152. if frappe.response:
  153. out.frappe.response = frappe.response
  154. out.update(safe_globals)
  155. # default writer allows write access
  156. out._write_ = _write
  157. out._getitem_ = _getitem
  158. out._getattr_ = _getattr
  159. # allow iterators and list comprehension
  160. out._getiter_ = iter
  161. out._iter_unpack_sequence_ = RestrictedPython.Guards.guarded_iter_unpack_sequence
  162. # add common python builtins
  163. out.update(get_python_builtins())
  164. return out
  165. def is_job_queued(job_name, queue="default"):
  166. '''
  167. :param job_name: used to identify a queued job, usually dotted path to function
  168. :param queue: should be either long, default or short
  169. '''
  170. site = frappe.local.site
  171. queued_jobs = get_jobs(site=site, queue=queue, key='job_name').get(site)
  172. return queued_jobs and job_name in queued_jobs
  173. def safe_enqueue(function, **kwargs):
  174. '''
  175. Enqueue function to be executed using a background worker
  176. Accepts frappe.enqueue params like job_name, queue, timeout, etc.
  177. in addition to params to be passed to function
  178. :param function: whitelised function or API Method set in Server Script
  179. '''
  180. return enqueue(
  181. 'frappe.utils.safe_exec.call_whitelisted_function',
  182. function=function,
  183. **kwargs
  184. )
  185. def call_whitelisted_function(function, **kwargs):
  186. '''Executes a whitelisted function or Server Script of type API'''
  187. return call_with_form_dict(lambda: execute_cmd(function), kwargs)
  188. def run_script(script, **kwargs):
  189. '''run another server script'''
  190. return call_with_form_dict(
  191. lambda: frappe.get_doc('Server Script', script).execute_method(),
  192. kwargs
  193. )
  194. def call_with_form_dict(function, kwargs):
  195. # temporarily update form_dict, to use inside below call
  196. form_dict = getattr(frappe.local, 'form_dict', frappe._dict())
  197. if kwargs:
  198. frappe.local.form_dict = form_dict.copy().update(kwargs)
  199. try:
  200. return function()
  201. finally:
  202. frappe.local.form_dict = form_dict
  203. def get_python_builtins():
  204. return {
  205. 'abs': abs,
  206. 'all': all,
  207. 'any': any,
  208. 'bool': bool,
  209. 'dict': dict,
  210. 'enumerate': enumerate,
  211. 'isinstance': isinstance,
  212. 'issubclass': issubclass,
  213. 'list': list,
  214. 'max': max,
  215. 'min': min,
  216. 'range': range,
  217. 'set': set,
  218. 'sorted': sorted,
  219. 'sum': sum,
  220. 'tuple': tuple,
  221. }
  222. def get_hooks(hook=None, default=None, app_name=None):
  223. hooks = frappe.get_hooks(hook=hook, default=default, app_name=app_name)
  224. return copy.deepcopy(hooks)
  225. def read_sql(query, *args, **kwargs):
  226. '''a wrapper for frappe.db.sql to allow reads'''
  227. query = str(query)
  228. if frappe.flags.in_safe_exec:
  229. check_safe_sql_query(query)
  230. return frappe.db.sql(query, *args, **kwargs)
  231. def check_safe_sql_query(query: str, throw: bool = True) -> bool:
  232. """ Check if SQL query is safe for running in restricted context.
  233. Safe queries:
  234. 1. Read only 'select' or 'explain' queries
  235. 2. CTE on mariadb where writes are not allowed.
  236. """
  237. query = query.strip().lower()
  238. whitelisted_statements = ("select", "explain")
  239. if (query.startswith(whitelisted_statements)
  240. or (query.startswith("with") and frappe.db.db_type == "mariadb")):
  241. return True
  242. if throw:
  243. frappe.throw(_("Query must be of SELECT or read-only WITH type."),
  244. title=_("Unsafe SQL query"), exc=frappe.PermissionError)
  245. return False
  246. def _getitem(obj, key):
  247. # guard function for RestrictedPython
  248. # allow any key to be accessed as long as it does not start with underscore
  249. if isinstance(key, str) and key.startswith('_'):
  250. raise SyntaxError('Key starts with _')
  251. return obj[key]
  252. def _getattr(object, name, default=None):
  253. # guard function for RestrictedPython
  254. # allow any key to be accessed as long as
  255. # 1. it does not start with an underscore (safer_getattr)
  256. # 2. it is not an UNSAFE_ATTRIBUTES
  257. UNSAFE_ATTRIBUTES = {
  258. # Generator Attributes
  259. "gi_frame", "gi_code",
  260. # Coroutine Attributes
  261. "cr_frame", "cr_code", "cr_origin",
  262. # Async Generator Attributes
  263. "ag_code", "ag_frame",
  264. # Traceback Attributes
  265. "tb_frame", "tb_next",
  266. }
  267. if isinstance(name, str) and (name in UNSAFE_ATTRIBUTES):
  268. raise SyntaxError("{name} is an unsafe attribute".format(name=name))
  269. return RestrictedPython.Guards.safer_getattr(object, name, default=default)
  270. def _write(obj):
  271. # guard function for RestrictedPython
  272. # allow writing to any object
  273. return obj
  274. def add_data_utils(data):
  275. for key, obj in frappe.utils.data.__dict__.items():
  276. if key in VALID_UTILS:
  277. data[key] = obj
  278. def add_module_properties(module, data, filter_method):
  279. for key, obj in module.__dict__.items():
  280. if key.startswith("_"):
  281. # ignore
  282. continue
  283. if filter_method(obj):
  284. # only allow functions
  285. data[key] = obj
  286. VALID_UTILS = (
  287. "DATE_FORMAT",
  288. "TIME_FORMAT",
  289. "DATETIME_FORMAT",
  290. "is_invalid_date_string",
  291. "getdate",
  292. "get_datetime",
  293. "to_timedelta",
  294. "get_timedelta",
  295. "add_to_date",
  296. "add_days",
  297. "add_months",
  298. "add_years",
  299. "date_diff",
  300. "month_diff",
  301. "time_diff",
  302. "time_diff_in_seconds",
  303. "time_diff_in_hours",
  304. "now_datetime",
  305. "get_timestamp",
  306. "get_eta",
  307. "get_time_zone",
  308. "convert_utc_to_user_timezone",
  309. "now",
  310. "nowdate",
  311. "today",
  312. "nowtime",
  313. "get_first_day",
  314. "get_quarter_start",
  315. "get_first_day_of_week",
  316. "get_year_start",
  317. "get_last_day_of_week",
  318. "get_last_day",
  319. "get_time",
  320. "get_datetime_in_timezone",
  321. "get_datetime_str",
  322. "get_date_str",
  323. "get_time_str",
  324. "get_user_date_format",
  325. "get_user_time_format",
  326. "format_date",
  327. "format_time",
  328. "format_datetime",
  329. "format_duration",
  330. "get_weekdays",
  331. "get_weekday",
  332. "get_timespan_date_range",
  333. "global_date_format",
  334. "has_common",
  335. "flt",
  336. "cint",
  337. "floor",
  338. "ceil",
  339. "cstr",
  340. "rounded",
  341. "remainder",
  342. "safe_div",
  343. "round_based_on_smallest_currency_fraction",
  344. "encode",
  345. "parse_val",
  346. "fmt_money",
  347. "get_number_format_info",
  348. "money_in_words",
  349. "in_words",
  350. "is_html",
  351. "is_image",
  352. "get_thumbnail_base64_for_image",
  353. "image_to_base64",
  354. "pdf_to_base64",
  355. "strip_html",
  356. "escape_html",
  357. "pretty_date",
  358. "comma_or",
  359. "comma_and",
  360. "comma_sep",
  361. "new_line_sep",
  362. "filter_strip_join",
  363. "get_url",
  364. "get_host_name_from_request",
  365. "url_contains_port",
  366. "get_host_name",
  367. "get_link_to_form",
  368. "get_link_to_report",
  369. "get_absolute_url",
  370. "get_url_to_form",
  371. "get_url_to_list",
  372. "get_url_to_report",
  373. "get_url_to_report_with_filters",
  374. "evaluate_filters",
  375. "compare",
  376. "get_filter",
  377. "make_filter_tuple",
  378. "make_filter_dict",
  379. "sanitize_column",
  380. "scrub_urls",
  381. "expand_relative_urls",
  382. "quoted",
  383. "quote_urls",
  384. "unique",
  385. "strip",
  386. "to_markdown",
  387. "md_to_html",
  388. "markdown",
  389. "is_subset",
  390. "generate_hash",
  391. "formatdate",
  392. "get_user_info_for_avatar",
  393. "get_abbr"
  394. )