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

Reply via email to