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

Reply via email to