This is an automated email from the ASF dual-hosted git repository. mridulpathak pushed a commit to branch release24.09 in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
commit 0a899c8bc0c1cb254c92c46a7a2e25b526ab5d45 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 | 44 ++++++++++++++++++++-- applications/order/servicedef/secas.xml | 2 +- applications/product/servicedef/secas.xml | 6 +-- .../shipment/ShipmentReceiptServices.groovy | 16 +++++--- 5 files changed, 65 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 80e4355eaf..cd04d0e6c0 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 @@ -223,7 +223,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, @@ -311,7 +315,11 @@ Map issueProductionRunTaskComponentInline(Map parameters, return serviceResult } parameters.quantityNotIssued -= parameters.deductAmount - serviceResult = run service: 'balanceInventoryItems', with: [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 = run service: 'balanceInventoryItems', with: [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 2f6942956c..c2330037ae 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)); @@ -1964,12 +1980,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 { @@ -2016,6 +2037,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")); @@ -2025,7 +2051,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()); } } @@ -2197,12 +2223,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 { @@ -2250,11 +2281,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 ffbe2d67d5..c90cb114fd 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 c52882fae1..14387bd364 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 @@ -178,10 +178,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 @@ -397,8 +400,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

