fix: regression issues related to security fixes

This commit is contained in:
Rohit Waghchaure
2026-06-14 23:41:59 +05:30
parent 70bb23d65b
commit be1aa0e5eb
8 changed files with 85 additions and 11 deletions

View File

@@ -507,10 +507,10 @@ 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})
return frappe.db.get_value(
"Bank Account", {"party_type": party_type, "party": party, "is_default": 1, "disabled": 0}, "name"
)
def get_party_account_currency(party_type, party, company):

View File

@@ -700,7 +700,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

@@ -1119,10 +1119,10 @@ class SubcontractingInwardController:
def update_inward_order_status(self):
if self.subcontracting_inward_order:
from erpnext.subcontracting.doctype.subcontracting_inward_order.subcontracting_inward_order import (
update_subcontracting_inward_order_status,
set_subcontracting_inward_order_status,
)
update_subcontracting_inward_order_status(self.subcontracting_inward_order)
set_subcontracting_inward_order_status(self.subcontracting_inward_order)
@frappe.whitelist()

View File

@@ -614,7 +614,7 @@ class SalesOrder(SellingController):
def update_subcontracting_order_status(self):
from erpnext.subcontracting.doctype.subcontracting_inward_order.subcontracting_inward_order import (
update_subcontracting_inward_order_status as update_scio_status,
set_subcontracting_inward_order_status as update_scio_status,
)
if self.is_subcontracted:

View File

@@ -3972,10 +3972,12 @@ class StockEntry(StockController, SubcontractingInwardController):
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

@@ -550,10 +550,18 @@ class SubcontractingInwardOrder(SubcontractingController):
return stock_entry.as_dict()
def set_subcontracting_inward_order_status(scio: str | Document, status: str | None = None):
if isinstance(scio, str):
scio = frappe.get_doc("Subcontracting Inward Order", scio)
scio.update_status(status)
@frappe.whitelist()
def update_subcontracting_inward_order_status(scio: str | Document, status: str | None = None):
"""Whitelisted boundary for direct API/UI calls — enforces write permission, then delegates."""
if isinstance(scio, str):
scio = frappe.get_doc("Subcontracting Inward Order", scio)
scio.check_permission("write")
scio.update_status(status)
set_subcontracting_inward_order_status(scio, status)

View File

@@ -483,10 +483,18 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None, items=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(ERPNextTestSuite):
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)