This is an automated email from the ASF dual-hosted git repository. mridulpathak pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
commit 83fa2c48d78047861ca5beefe86f070934fcf694 Author: Mridul Pathak <[email protected]> AuthorDate: Mon Oct 26 13:14:11 2026 +0530 Run internal balanceInventoryItems and createCostComponent calls as the system user The permission checks this PR added to balanceInventoryItems, setOrderReservationPriority, and createCostComponent broke several internal callers that invoke these services as a trusted side effect of an already-permitted operation, none of which hold the staff-level permission these services now require. - ProductionRunServices.java: 4 balanceInventoryItems calls and 5 createCostComponent calls, from production-run status-change and cost-calculation flows - ProductionRunServicesScript.groovy: 2 balanceInventoryItems calls, from production-run task issuance - ShipmentReceiptServices.groovy: 2 balanceInventoryItems calls, from shipment-receipt processing - order/servicedef/secas.xml and product/servicedef/secas.xml: 4 ECA actions (storeOrder, createInventoryTransfer, updateInventoryTransfer, createPhysicalInventoryAndVariance) now run their balanceInventoryItems/setOrderReservationPriority action as the system user Each caller now resolves the system UserLogin and runs that specific call under it, following the pattern already used elsewhere in the codebase for trusted internal side effects. --- .../jobshopmgt/ProductionRunServicesScript.groovy | 12 +++++- .../jobshopmgt/ProductionRunServices.java | 48 ++++++++++++++++++++-- applications/order/servicedef/secas.xml | 2 +- applications/product/servicedef/secas.xml | 6 +-- .../shipment/ShipmentReceiptServices.groovy | 16 +++++--- 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/applications/manufacturing/src/main/groovy/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServicesScript.groovy b/applications/manufacturing/src/main/groovy/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServicesScript.groovy index e8748fdcb1..76cfe58138 100644 --- a/applications/manufacturing/src/main/groovy/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServicesScript.groovy +++ b/applications/manufacturing/src/main/groovy/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServicesScript.groovy @@ -432,7 +432,11 @@ Map issueProductionRunTaskComponent() { quantityOnHandDiff: -parameters.quantityNotIssued, reasonEnumId: parameters.reasonEnumId, description: parameters.description] - run service: 'balanceInventoryItems', with: [inventoryItemId: lastNonSerInventoryItem.inventoryItemId] + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run operation + GenericValue balanceInventoryPermUserLogin = from('UserLogin').where(userLoginId: 'system').cache().queryOne() + run service: 'balanceInventoryItems', with: [inventoryItemId: lastNonSerInventoryItem.inventoryItemId, + userLogin: balanceInventoryPermUserLogin] } else { // no non-ser inv item, create a non-ser InventoryItem with availableToPromise = -quantityNotIssued Map serviceResult = run service: 'createInventoryItem', with: [productId: productId, @@ -656,7 +660,11 @@ Map issueProductionRunTaskComponentInline(Map parameters, parameters.workEffortId, strategy, parameters.failIfItemsAreNotAvailable, taskAllowReallocation) parameters.quantityNotIssued = ((BigDecimal) parameters.quantityNotIssued).subtract(deductAmount) - serviceResult = runService('balanceInventoryItems', [inventoryItemId: inventoryItem.inventoryItemId]) + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run operation + GenericValue balanceInventoryPermUserLogin = from('UserLogin').where(userLoginId: 'system').cache().queryOne() + serviceResult = runService('balanceInventoryItems', [inventoryItemId: inventoryItem.inventoryItemId, + userLogin: balanceInventoryPermUserLogin]) if (ServiceUtil.isError(serviceResult)) { return serviceResult } diff --git a/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServices.java b/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServices.java index 56c599cfb2..423bbfa5ec 100644 --- a/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServices.java +++ b/applications/manufacturing/src/main/java/org/apache/ofbiz/manufacturing/jobshopmgt/ProductionRunServices.java @@ -1068,6 +1068,10 @@ public class ProductionRunServices { inMap.put("costComponentTypeId", "ACTUAL_" + productCostComponentCalc.getString("costComponentTypeId")); inMap.put("costUomId", partyAccountingPreference.get("baseCurrencyUomId")); inMap.put("cost", productCostAdjustment); + // createCostComponent requires CATALOG_CREATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run cost calculation + inMap.put("userLogin", EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne()); serviceResult = dispatcher.runSync("createCostComponent", inMap); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); @@ -1233,6 +1237,10 @@ public class ProductionRunServices { inMap.put("costComponentCalcId", costComponentCalc.getString("costComponentCalcId")); inMap.put("costUomId", costComponentCalc.getString("currencyUomId")); inMap.put("cost", totalCost); + // createCostComponent requires CATALOG_CREATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run cost calculation + inMap.put("userLogin", EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne()); serviceResult = dispatcher.runSync("createCostComponent", inMap); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); @@ -1280,6 +1288,10 @@ public class ProductionRunServices { inMap.put("costUomId", currencyUomId); inMap.put("cost", fixedAssetCost); inMap.put("fixedAssetId", fixedAsset.get("fixedAssetId")); + // createCostComponent requires CATALOG_CREATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run cost calculation + inMap.put("userLogin", EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne()); serviceResult = dispatcher.runSync("createCostComponent", inMap); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); @@ -1338,6 +1350,10 @@ public class ProductionRunServices { inMap.put("costComponentTypeId", "ACTUAL_MAT_COST"); inMap.put("costUomId", currencyUomId); inMap.put("cost", materialsCost); + // createCostComponent requires CATALOG_CREATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run cost calculation + inMap.put("userLogin", EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne()); serviceResult = dispatcher.runSync("createCostComponent", inMap); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); @@ -1379,6 +1395,10 @@ public class ProductionRunServices { inMap.put("costComponentTypeId", "ACTUAL_LABOR_COST"); inMap.put("costUomId", rateAmount.getString("rateCurrencyUomId")); inMap.put("cost", laborCost); + // createCostComponent requires CATALOG_CREATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run cost calculation + inMap.put("userLogin", EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne()); serviceResult = dispatcher.runSync("createCostComponent", inMap); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); @@ -2015,12 +2035,17 @@ public class ProductionRunServices { serviceContext = new HashMap<>(); serviceContext.put("inventoryItemId", inventoryItemId); serviceContext.put("userLogin", userLogin); + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run operation + GenericValue balanceInventoryPermUserLogin = EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne(); + serviceContext.put("userLogin", balanceInventoryPermUserLogin); serviceResult = dispatcher.runSync("balanceInventoryItems", serviceContext); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); } } - } catch (GenericServiceException exc) { + } catch (GenericServiceException | GenericEntityException exc) { return ServiceUtil.returnError(exc.getMessage()); } } else { @@ -2067,6 +2092,11 @@ public class ProductionRunServices { serviceContext = new HashMap<>(); serviceContext.put("inventoryItemId", inventoryItemId); serviceContext.put("userLogin", userLogin); + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run operation + GenericValue balanceInventoryPermUserLogin = EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne(); + serviceContext.put("userLogin", balanceInventoryPermUserLogin); if (orderItem != null) { // the reservations of this order item are privileged reservations serviceContext.put("priorityOrderId", orderItem.getString("orderId")); @@ -2076,7 +2106,7 @@ public class ProductionRunServices { if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); } - } catch (GenericServiceException exc) { + } catch (GenericServiceException | GenericEntityException exc) { return ServiceUtil.returnError(exc.getMessage()); } } @@ -2248,12 +2278,17 @@ public class ProductionRunServices { serviceContext = new HashMap<>(); serviceContext.put("inventoryItemId", inventoryItemId); serviceContext.put("userLogin", userLogin); + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run operation + GenericValue balanceInventoryPermUserLogin = EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne(); + serviceContext.put("userLogin", balanceInventoryPermUserLogin); serviceResult = dispatcher.runSync("balanceInventoryItems", serviceContext); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); } } - } catch (GenericServiceException exc) { + } catch (GenericServiceException | GenericEntityException exc) { return ServiceUtil.returnError(exc.getMessage()); } } else { @@ -2301,11 +2336,16 @@ public class ProductionRunServices { serviceContext = new HashMap<>(); serviceContext.put("inventoryItemId", inventoryItemId); serviceContext.put("userLogin", userLogin); + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side + // effect of an already-permitted production-run operation + GenericValue balanceInventoryPermUserLogin = EntityQuery.use(delegator).from("UserLogin") + .where("userLoginId", "system").cache().queryOne(); + serviceContext.put("userLogin", balanceInventoryPermUserLogin); serviceResult = dispatcher.runSync("balanceInventoryItems", serviceContext); if (ServiceUtil.isError(serviceResult)) { return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult)); } - } catch (GenericServiceException exc) { + } catch (GenericServiceException | GenericEntityException exc) { return ServiceUtil.returnError(exc.getMessage()); } } diff --git a/applications/order/servicedef/secas.xml b/applications/order/servicedef/secas.xml index e83185ee26..0342e03cd3 100644 --- a/applications/order/servicedef/secas.xml +++ b/applications/order/servicedef/secas.xml @@ -39,7 +39,7 @@ under the License. </eca> <eca service="storeOrder" event="return"> - <action service="setOrderReservationPriority" mode="sync"/> + <action service="setOrderReservationPriority" mode="sync" run-as-user="system"/> </eca> <eca service="updateOrderItems" event="commit"> diff --git a/applications/product/servicedef/secas.xml b/applications/product/servicedef/secas.xml index 2ca07b6ff5..afda290d2b 100644 --- a/applications/product/servicedef/secas.xml +++ b/applications/product/servicedef/secas.xml @@ -52,7 +52,7 @@ under the License. <eca service="createInventoryTransfer" event="commit"> <condition field-name="statusId" operator="equals" value="IXF_COMPLETE"/> <action service="completeInventoryTransfer" mode="sync"/> - <action service="balanceInventoryItems" mode="sync"/> + <action service="balanceInventoryItems" mode="sync" run-as-user="system"/> </eca> <eca service="updateInventoryTransfer" event="invoke"> @@ -62,12 +62,12 @@ under the License. <eca service="updateInventoryTransfer" event="commit"> <condition field-name="statusId" operator="equals" value="IXF_COMPLETE"/> <action service="completeInventoryTransfer" mode="sync"/> - <action service="balanceInventoryItems" mode="sync"/> + <action service="balanceInventoryItems" mode="sync" run-as-user="system"/> </eca> <!-- inventory variance --> <eca service="createPhysicalInventoryAndVariance" event="commit"> - <action service="balanceInventoryItems" mode="sync"/> + <action service="balanceInventoryItems" mode="sync" run-as-user="system"/> </eca> <!-- inventory changes --> diff --git a/applications/product/src/main/groovy/org/apache/ofbiz/product/shipment/ShipmentReceiptServices.groovy b/applications/product/src/main/groovy/org/apache/ofbiz/product/shipment/ShipmentReceiptServices.groovy index cafb44cb7c..7bdc89d89e 100644 --- a/applications/product/src/main/groovy/org/apache/ofbiz/product/shipment/ShipmentReceiptServices.groovy +++ b/applications/product/src/main/groovy/org/apache/ofbiz/product/shipment/ShipmentReceiptServices.groovy @@ -174,10 +174,13 @@ Map receiveInventoryProduct () { } } if (parameters.quantityAccepted > BigDecimal.ZERO) { - serviceInMap = [:] - serviceInMap = parameters - serviceInMap.inventoryItemId = currentInventoryItemId - run service: 'balanceInventoryItems', with: serviceInMap + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side effect of an + // already-permitted shipment-receipt operation. A copy (not an alias of parameters/serviceInMap) is used here so this + // elevation doesn't leak into later iterations' calls that reuse the shared parameters map. + Map balanceInventoryMap = new HashMap(parameters) + balanceInventoryMap.inventoryItemId = currentInventoryItemId + balanceInventoryMap.userLogin = from('UserLogin').where(userLoginId: 'system').cache().queryOne() + run service: 'balanceInventoryItems', with: balanceInventoryMap successMessageList << "Received ${parameters.quantityAccepted} of ${parameters.productId}" .concat(" in inventory item ${currentInventoryItemId}.") as String @@ -390,8 +393,11 @@ Map cancelReceivedItems() { run service: 'createInventoryItemDetail', with: inventoryItemDetailMap // Balance the inventory item + // balanceInventoryItems requires ORDERMGR_UPDATE; run as the "system" user since this is a trusted side + // effect of an already-permitted shipment-receipt operation Map balanceInventoryItemMap = [inventoryItemId: inventoryItem.inventoryItemId, - priorityOrderId: shipmentReceipt.orderId, priorityOrderItemSeqId: shipmentReceipt.orderItemSeqId] + priorityOrderId: shipmentReceipt.orderId, priorityOrderItemSeqId: shipmentReceipt.orderItemSeqId, + userLogin: from('UserLogin').where(userLoginId: 'system').cache().queryOne()] run service: 'balanceInventoryItems', with: balanceInventoryItemMap // update the shipment status, if shipment was received

