mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 07:01:56 +00:00
fix: regression issues related to security fixes
This commit is contained in:
@@ -507,10 +507,10 @@ def get_party_advance_account(party_type, party, company):
|
|||||||
return account
|
return account
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_party_bank_account(party_type: str, party: str):
|
def get_party_bank_account(party_type: str, party: str):
|
||||||
frappe.has_permission("Bank Account", "read", throw=True)
|
return frappe.db.get_value(
|
||||||
return frappe.db.get_value("Bank Account", {"party_type": party_type, "party": party, "is_default": 1})
|
"Bank Account", {"party_type": party_type, "party": party, "is_default": 1, "disabled": 0}, "name"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_party_account_currency(party_type, party, company):
|
def get_party_account_currency(party_type, party, company):
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
|
|
||||||
def update_subcontracting_order_status(self):
|
def update_subcontracting_order_status(self):
|
||||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
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:
|
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||||
|
|||||||
@@ -1119,10 +1119,10 @@ class SubcontractingInwardController:
|
|||||||
def update_inward_order_status(self):
|
def update_inward_order_status(self):
|
||||||
if self.subcontracting_inward_order:
|
if self.subcontracting_inward_order:
|
||||||
from erpnext.subcontracting.doctype.subcontracting_inward_order.subcontracting_inward_order import (
|
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()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -614,7 +614,7 @@ class SalesOrder(SellingController):
|
|||||||
|
|
||||||
def update_subcontracting_order_status(self):
|
def update_subcontracting_order_status(self):
|
||||||
from erpnext.subcontracting.doctype.subcontracting_inward_order.subcontracting_inward_order import (
|
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:
|
if self.is_subcontracted:
|
||||||
|
|||||||
@@ -3972,10 +3972,12 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
def update_subcontracting_order_status(self):
|
def update_subcontracting_order_status(self):
|
||||||
if self.subcontracting_order and self.purpose in ["Send to Subcontractor", "Material Transfer"]:
|
if self.subcontracting_order and self.purpose in ["Send to Subcontractor", "Material Transfer"]:
|
||||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
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):
|
def update_pick_list_status(self):
|
||||||
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
|
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
|
||||||
|
|||||||
@@ -550,10 +550,18 @@ class SubcontractingInwardOrder(SubcontractingController):
|
|||||||
return stock_entry.as_dict()
|
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()
|
@frappe.whitelist()
|
||||||
def update_subcontracting_inward_order_status(scio: str | Document, status: str | None = None):
|
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):
|
if isinstance(scio, str):
|
||||||
scio = frappe.get_doc("Subcontracting Inward Order", scio)
|
scio = frappe.get_doc("Subcontracting Inward Order", scio)
|
||||||
|
|
||||||
scio.check_permission("write")
|
scio.check_permission("write")
|
||||||
scio.update_status(status)
|
set_subcontracting_inward_order_status(scio, status)
|
||||||
|
|||||||
@@ -483,10 +483,18 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None, items=None):
|
|||||||
return target_doc
|
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()
|
@frappe.whitelist()
|
||||||
def update_subcontracting_order_status(sco: str | Document, status: str | None = None):
|
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):
|
if isinstance(sco, str):
|
||||||
sco = frappe.get_doc("Subcontracting Order", sco)
|
sco = frappe.get_doc("Subcontracting Order", sco)
|
||||||
|
|
||||||
sco.check_permission("write")
|
sco.check_permission("write")
|
||||||
sco.update_status(status)
|
set_subcontracting_order_status(sco, status)
|
||||||
|
|||||||
@@ -336,6 +336,62 @@ class TestSubcontractingOrder(ERPNextTestSuite):
|
|||||||
bin_after_cancel_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract
|
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):
|
def test_exploded_items(self):
|
||||||
item_code = "_Test Subcontracted FG Item 11"
|
item_code = "_Test Subcontracted FG Item 11"
|
||||||
make_subcontracted_item(item_code=item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
|
|||||||
Reference in New Issue
Block a user