From 7ce1289c10e06095c968720dc62cd0786c21b601 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:10:27 +0530 Subject: [PATCH] fix: added permission checks on various whitelisted functions (backport #56745) (#56946) Co-authored-by: Diptanil Saha --- .../doctype/bank_account/bank_account.py | 10 ++- .../doctype/payment_entry/payment_entry.py | 5 +- erpnext/accounts/party.py | 78 +++++++++++-------- erpnext/tests/utils.py | 1 + 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 0697a1a9c64..c58af6b88f3 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -107,7 +107,7 @@ def get_party_bank_account(party_type, party): ) -def get_default_company_bank_account(company, party_type, party): +def get_default_company_bank_account(company, party_type, party, ignore_permissions=True): default_company_bank_account = frappe.db.get_value(party_type, party, "default_bank_account") if default_company_bank_account: if company != frappe.get_cached_value("Bank Account", default_company_bank_account, "company"): @@ -118,6 +118,14 @@ def get_default_company_bank_account(company, party_type, party): "Bank Account", {"company": company, "is_company_account": 1, "is_default": 1} ) + if not ignore_permissions: + default_company_bank_account = ( + default_company_bank_account + if default_company_bank_account + and frappe.get_cached_doc("Bank Account", default_company_bank_account).has_permission("select") + else None + ) + return default_company_bank_account diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 4d218d272b3..9293e7fb0b8 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -2712,6 +2712,9 @@ def get_party_details(company, party_type, party, date, cost_center=None): if not frappe.db.exists(party_type, party): frappe.throw(_("{0} {1} does not exist").format(_(party_type), party)) + ptype = "select" if frappe.only_has_select_perm(party_type) else "read" + frappe.has_permission(party_type, ptype, party, throw=True) + party_account = get_party_account(party_type, party, company) account_currency = get_account_currency(party_account) _party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name" @@ -2719,7 +2722,7 @@ def get_party_details(company, party_type, party, date, cost_center=None): if party_type in ["Customer", "Supplier"]: party_bank_account = get_party_bank_account(party_type, party) - bank_account = get_default_company_bank_account(company, party_type, party) + bank_account = get_default_company_bank_account(company, party_type, party, ignore_permissions=False) return { "party_account": party_account, diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 1c1b166c605..7b726604d8e 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -428,6 +428,17 @@ def get_party_account(party_type, party=None, company=None, include_advance=Fals Will first search in party (Customer / Supplier) record, if not found, will search in group (Customer Group / Supplier Group), finally will return default.""" + + def account_perm_check(account): + ptype = "select" if frappe.only_has_select_perm("Account") else "read" + if frappe.has_permission("Account", ptype, account): + return + + # Using custom message to prevent data leak in case of `apply_strict_permission` is enabled. + frappe.throw( + _("User don't have permissions to select/read this account."), exc=frappe.PermissionError + ) + if not party_type: frappe.throw(_("Party Type is mandatory")) if not company: @@ -438,46 +449,51 @@ def get_party_account(party_type, party=None, company=None, include_advance=Fals "default_receivable_account" if party_type == "Customer" else "default_payable_account" ) - return frappe.get_cached_value("Company", company, default_account_name) - - account = frappe.db.get_value( - "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account" - ) - - if not account and party_type in ["Customer", "Supplier"]: - party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group" - group = frappe.get_cached_value(party_type, party, scrub(party_group_doctype)) + account = frappe.get_cached_value("Company", company, default_account_name) + else: account = frappe.db.get_value( - "Party Account", - {"parenttype": party_group_doctype, "parent": group, "company": company}, - "account", + "Party Account", {"parenttype": party_type, "parent": party, "company": company}, "account" ) - if not account and party_type in ["Customer", "Supplier"]: - default_account_name = ( - "default_receivable_account" if party_type == "Customer" else "default_payable_account" - ) - account = frappe.get_cached_value("Company", company, default_account_name) + if not account and party_type in ["Customer", "Supplier"]: + party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group" + group = frappe.get_cached_value(party_type, party, scrub(party_group_doctype)) + account = frappe.db.get_value( + "Party Account", + {"parenttype": party_group_doctype, "parent": group, "company": company}, + "account", + ) - existing_gle_currency = get_party_gle_currency(party_type, party, company) - if existing_gle_currency: - if account: - account_currency = frappe.get_cached_value("Account", account, "account_currency") - if (account and account_currency != existing_gle_currency) or not account: - account = get_party_gle_account(party_type, party, company) + if not account and party_type in ["Customer", "Supplier"]: + default_account_name = ( + "default_receivable_account" if party_type == "Customer" else "default_payable_account" + ) + account = frappe.get_cached_value("Company", company, default_account_name) - # get default account on the basis of party type - if not account: - account_type = frappe.get_cached_value("Party Type", party_type, "account_type") - default_account_name = "default_" + account_type.lower() + "_account" - account = frappe.get_cached_value("Company", company, default_account_name) + existing_gle_currency = get_party_gle_currency(party_type, party, company) + if existing_gle_currency: + if account: + account_currency = frappe.get_cached_value("Account", account, "account_currency") + if (account and account_currency != existing_gle_currency) or not account: + account = get_party_gle_account(party_type, party, company) - if include_advance and party_type in ["Customer", "Supplier", "Student"]: + # get default account on the basis of party type + if not account: + account_type = frappe.get_cached_value("Party Type", party_type, "account_type") + default_account_name = "default_" + account_type.lower() + "_account" + account = frappe.get_cached_value("Company", company, default_account_name) + + if account: + account_perm_check(account) + + if include_advance and party and party_type in ["Customer", "Supplier", "Student"]: advance_account = get_party_advance_account(party_type, party, company) + if advance_account: + account_perm_check(advance_account) return [account, advance_account] - else: - return [account] + + return [account] return account diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 99c55b6d7ba..a48f193c700 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -462,6 +462,7 @@ class BootStrapTestData: "new_password": "Eastern_43A1W", "roles": [ {"doctype": "Has Role", "parentfield": "roles", "role": "_Test Role"}, + {"doctype": "Has Role", "parentfield": "roles", "role": "Accounts User"}, {"doctype": "Has Role", "parentfield": "roles", "role": "System Manager"}, ], },