fix: regression issues related to security fixes (backport #55902) (#55925)

* fix: regression issues related to security fixes

(cherry picked from commit be1aa0e5eb)

# Conflicts:
#	erpnext/controllers/subcontracting_inward_controller.py
#	erpnext/selling/doctype/sales_order/sales_order.py
#	erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py

* refactor: consolidate duplicate get_party_bank_account into bank_account.py

(cherry picked from commit ede13cb3bd)

* chore: fix conflicts

Removed duplicate import of get_party_bank_account and consolidated imports.

* chore: fix conflicts

Removed the update_subcontracting_order_status method to address regression issues related to security fixes.

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
This commit is contained in:
mergify[bot]
2026-06-15 11:23:08 +00:00
committed by GitHub
parent b4be0834f2
commit 1f47b2417b
6 changed files with 72 additions and 11 deletions

View File

@@ -11,11 +11,12 @@ from erpnext import get_company_currency
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_payment_entry,
)
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
from erpnext.accounts.party import get_party_account, get_party_bank_account
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import get_account_currency, get_currency_precision
from erpnext.utilities import payment_app_import_guard

View File

@@ -510,12 +510,6 @@ def get_party_advance_account(party_type, party, company):
return account
@frappe.whitelist()
def get_party_bank_account(party_type: str, party: str):
frappe.has_permission("Bank Account", "read", throw=True)
return frappe.db.get_value("Bank Account", {"party_type": party_type, "party": party, "is_default": 1})
def get_party_account_currency(party_type, party, company):
def generator():
party_account = get_party_account(party_type, party, company)

View File

@@ -662,7 +662,7 @@ class PurchaseOrder(BuyingController):
def update_subcontracting_order_status(self):
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
update_subcontracting_order_status as update_sco_status,
set_subcontracting_order_status as update_sco_status,
)
if self.is_subcontracted and not self.is_old_subcontracting_flow:

View File

@@ -3410,10 +3410,12 @@ class StockEntry(StockController):
def update_subcontracting_order_status(self):
if self.subcontracting_order and self.purpose in ["Send to Subcontractor", "Material Transfer"]:
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
update_subcontracting_order_status,
set_subcontracting_order_status,
)
update_subcontracting_order_status(self.subcontracting_order)
# Trusted submit/cancel flow — a Stock operation must not require Subcontracting Order
# write permission, so use the no-check internal helper (not the whitelisted boundary).
set_subcontracting_order_status(self.subcontracting_order)
def update_pick_list_status(self):
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status

View File

@@ -364,10 +364,18 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None):
return target_doc
def set_subcontracting_order_status(sco: str | Document, status: str | None = None):
if isinstance(sco, str):
sco = frappe.get_doc("Subcontracting Order", sco)
sco.update_status(status)
@frappe.whitelist()
def update_subcontracting_order_status(sco: str | Document, status: str | None = None):
"""Whitelisted boundary for direct API/UI calls — enforces write permission, then delegates."""
if isinstance(sco, str):
sco = frappe.get_doc("Subcontracting Order", sco)
sco.check_permission("write")
sco.update_status(status)
set_subcontracting_order_status(sco, status)

View File

@@ -336,6 +336,62 @@ class TestSubcontractingOrder(FrappeTestCase):
bin_after_cancel_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract
)
def test_send_to_subcontractor_ste_submit_without_sco_write_permission(self):
"""A Stock-only user (can submit Stock Entries but has no Subcontracting Order write) must be
able to submit and cancel a 'Send to Subcontractor' Stock Entry. The SCO status update on the
on_submit/on_cancel path goes through the no-permission-check internal helper, not the
whitelisted API boundary.
Regression: the permission hardening put check_permission('write') on the shared status
function, so a Stock Manager (no SCO write) hit PermissionError submitting/cancelling the
Stock Entry. The suite otherwise runs as Administrator and never caught it."""
from frappe.core.doctype.user_permission.test_user_permission import create_user
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100)
service_items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Service Item 1",
"qty": 10,
"rate": 100,
"fg_item": "_Test FG Item",
"fg_item_qty": 10,
},
]
sco = get_subcontracting_order(service_items=service_items)
rm_items = [
{
"item_code": "_Test FG Item",
"rm_item_code": "_Test Item",
"item_name": "_Test Item",
"qty": 10,
"warehouse": "_Test Warehouse - _TC",
"rate": 100,
"amount": 1000,
"stock_uom": "Nos",
},
]
ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
ste.to_warehouse = "_Test Warehouse 1 - _TC"
ste.save()
stock_user = create_user("test_sco_stock_only@example.com", "Stock Manager")
self.assertFalse(
frappe.has_permission("Subcontracting Order", "write", user=stock_user.name),
"Precondition: the Stock-only user must not have Subcontracting Order write permission.",
)
frappe.set_user(stock_user.name)
try:
ste.reload()
ste.submit() # must not raise PermissionError on the SCO status update
ste.reload()
ste.cancel() # same on the cancel path
finally:
frappe.set_user("Administrator")
def test_exploded_items(self):
item_code = "_Test Subcontracted FG Item 11"
make_subcontracted_item(item_code=item_code)