From ba936eefabb784805daa4c602b4baec9fc243ff8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 8 Jun 2026 14:49:32 +0530 Subject: [PATCH] fix: Add authorization checks on internal functions (#55709) --- erpnext/accounts/doctype/account/account.py | 4 ++- .../bisect_accounting_statements.py | 2 +- .../doctype/pos_profile/pos_profile.py | 29 ------------------- .../process_payment_reconciliation.py | 3 ++ .../process_period_closing_voucher.py | 1 + .../process_statement_of_accounts.py | 1 + .../purchase_invoice/purchase_invoice.py | 6 ++-- erpnext/accounts/utils.py | 1 + .../supplier_scorecard/supplier_scorecard.py | 1 + .../supplier_quotation_comparison.py | 1 + erpnext/crm/doctype/lead/lead.py | 2 +- erpnext/projects/doctype/project/project.py | 2 +- erpnext/setup/doctype/employee/employee.py | 1 + .../doctype/delivery_trip/delivery_trip.py | 1 + .../stock_reposting_settings.py | 2 +- erpnext/support/doctype/issue/issue.py | 1 - 16 files changed, 20 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 491b8c2c456..c17119ad8a8 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -592,10 +592,12 @@ def update_account_number( @frappe.whitelist() def merge_account(old: str, new: str): _ensure_idle_system() - # Validate properties before merging new_account = frappe.get_cached_doc("Account", new) old_account = frappe.get_cached_doc("Account", old) + new_account.check_permission("write") + old_account.check_permission("write") + if not new_account: throw(_("Account {0} does not exist").format(new)) diff --git a/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py b/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py index e9860a5b5b4..ad3adadc4d6 100644 --- a/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py +++ b/erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py @@ -121,7 +121,7 @@ class BisectAccountingStatements(Document): cur_node.save() - @frappe.whitelist() + @frappe.whitelist(methods=["POST"]) def build_tree(self): frappe.db.delete("Bisect Nodes") diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 39a7694850a..3b7801bf11e 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -315,32 +315,3 @@ def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page ) return pos_profile - - -@frappe.whitelist() -def set_default_profile(pos_profile: str, company: str): - modified = now() - user = frappe.session.user - - if pos_profile and company: - frappe.db.sql( - """ update `tabPOS Profile User` pfu, `tabPOS Profile` pf - set - pfu.default = 0, pf.modified = %s, pf.modified_by = %s - where - pfu.user = %s and pf.name = pfu.parent and pf.company = %s - and pfu.default = 1""", - (modified, user, user, company), - auto_commit=1, - ) - - frappe.db.sql( - """ update `tabPOS Profile User` pfu, `tabPOS Profile` pf - set - pfu.default = 1, pf.modified = %s, pf.modified_by = %s - where - pfu.user = %s and pf.name = pfu.parent and pf.company = %s and pf.name = %s - """, - (modified, user, user, company, pos_profile), - auto_commit=1, - ) diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index 91eaf67d083..d110f9df8a2 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -131,6 +131,7 @@ def is_job_running(job_name: str) -> bool: @frappe.whitelist() def pause_job_for_doc(docname: str | None = None): if docname: + frappe.has_permission("Process Payment Reconciliation", "write", doc=docname, throw=True) frappe.db.set_value("Process Payment Reconciliation", docname, "status", "Paused") log = frappe.db.get_value("Process Payment Reconciliation Log", filters={"process_pr": docname}) if log: @@ -145,6 +146,8 @@ def trigger_job_for_doc(docname: str | None = None): if not docname: return + frappe.has_permission("Process Payment Reconciliation", "write", doc=docname, throw=True) + if not frappe.get_single_value("Accounts Settings", "auto_reconcile_payments"): frappe.throw( _("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format( diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 296c57bdf0c..801c895f37c 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -92,6 +92,7 @@ class ProcessPeriodClosingVoucher(Document): @frappe.whitelist() def start_pcv_processing(docname: str): if frappe.db.get_value("Process Period Closing Voucher", docname, "status") in ["Queued", "Running"]: + frappe.has_permission("Process Payment Reconciliation", "write", doc=docname, throw=True) frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Running") ppcvd = qb.DocType("Process Period Closing Voucher Detail") diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 52d05d856cd..9cd15cb76f7 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -521,6 +521,7 @@ def download_statements(document_name: str): @frappe.whitelist() def send_emails(document_name: str, from_scheduler: bool = False, posting_date: str | None = None): doc = frappe.get_doc("Process Statement Of Accounts", document_name) + doc.check_permission() report = get_report_pdf(doc, consolidated=False) if report: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f80bdca2d7d..713649cf788 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -934,9 +934,9 @@ def make_regional_gl_entries(gl_entries, doc): @frappe.whitelist() def change_release_date(name: str, release_date: str | None = None): - if frappe.db.exists("Purchase Invoice", name): - pi = frappe.get_lazy_doc("Purchase Invoice", name) - pi.db_set("release_date", release_date) + pi = frappe.get_lazy_doc("Purchase Invoice", name) + pi.check_permission("write") + pi.db_set("release_date", release_date) @frappe.whitelist() diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 2c258e027ee..9702c78f5cc 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1552,6 +1552,7 @@ def update_cost_center( Renames the document by adding the number as a prefix to the current name and updates all transaction where it was present. """ + frappe.has_permission("Cost Center", "write", doc=docname, throw=True) validate_field_number("Cost Center", docname, cost_center_number, company, "cost_center_number") if cost_center_number: diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py index ae32c2537d3..fb3a4b10396 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py @@ -180,6 +180,7 @@ def refresh_scorecards(): def make_all_scorecards(docname: str): sc = frappe.get_doc("Supplier Scorecard", docname) supplier = frappe.get_doc("Supplier", sc.supplier) + supplier.check_permission("write") start_date = getdate(supplier.creation) end_date = get_scorecard_date(sc.period, start_date) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 273d5c90982..5cf2743b190 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -296,6 +296,7 @@ def get_message(): @frappe.whitelist() def set_default_supplier(item_code: str, supplier: str, company: str): + frappe.has_permission("Item", "write", doc=item_code, throw=True) frappe.db.set_value( "Item Default", {"parent": item_code, "company": company}, diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d62e81271b4..7af85d5ee1a 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -386,7 +386,7 @@ def get_lead_with_phone_number(number): def add_lead_to_prospect(lead: str, prospect: str): prospect = frappe.get_doc("Prospect", prospect) prospect.append("leads", {"lead": lead}) - prospect.save(ignore_permissions=True) + prospect.save() carry_forward_communication_and_comments = frappe.db.get_single_value( "CRM Settings", "carry_forward_communication_and_comments" diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index da05691d3fa..081ac5dd96c 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -754,7 +754,7 @@ def set_project_status(project: str, status: str): frappe.throw(_("Status must be Cancelled or Completed")) project = frappe.get_doc("Project", project) - frappe.has_permission(doc=project, throw=True) + project.check_permission("write") for task in frappe.get_all("Task", dict(project=project.name)): frappe.db.set_value("Task", task.name, "status", status) diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 850bc3f1b44..7e42a817aa8 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -427,6 +427,7 @@ def deactivate_sales_person(status: str, employee: str): @frappe.whitelist() def create_user(employee: str, email: str | None = None, create_user_permission: int = 0) -> str: emp = frappe.get_doc("Employee", employee) + emp.check_permission("write") if emp.user_id: frappe.throw(_("Employee {0} already has a linked user").format(emp.name)) diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index e59472c5294..48efe8171d0 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -405,6 +405,7 @@ def sanitize_address(address): @frappe.whitelist() def notify_customers(delivery_trip: str): delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip) + delivery_trip.check_permission() context = delivery_trip.as_dict() diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py index 8976d260ff9..9164f8498cb 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py @@ -59,7 +59,7 @@ class StockRepostingSettings(Document): if diff < 10: self.end_time = get_time_str(add_to_date(self.start_time, hours=10, as_datetime=True)) - @frappe.whitelist() + @frappe.whitelist(methods=["POST"]) def convert_to_item_wh_reposting(self): """Convert Transaction reposting to Item Warehouse based reposting if Item Based Reposting has enabled.""" diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 75822ff2247..bd4e0eb5767 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -119,7 +119,6 @@ class Issue(Document): @frappe.whitelist() def split_issue(self, subject: str, communication_id: str): - # Bug: Pressing enter doesn't send subject from copy import deepcopy replicated_issue = deepcopy(self)