You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by mr...@apache.org on 2026/08/17 04:42:39 UTC

(ofbiz-framework) branch trunk updated: Improved: Validate tenant ID before creating a per-tenant delegator (#1647)

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


The following commit(s) were added to refs/heads/trunk by this push:
     new 53a8767b54 Improved: Validate tenant ID before creating a per-tenant delegator (#1647)
53a8767b54 is described below

commit 53a8767b54a621a26be7a0044ca20fcbb4fad21d
Author: Mridul Pathak <mr...@gmail.com>
AuthorDate: Mon Aug 17 10:12:33 2026 +0530

    Improved: Validate tenant ID before creating a per-tenant delegator (#1647)
    
    ContextFilter and LoginWorker now confirm a request-supplied userTenantId matches an enabled Tenant record before asking DelegatorFactory to build a delegator for it, instead of doing so unconditionally and only reacting to the failure afterward. DelegatorFactory also now evicts a delegator name from its cache when creation fails, so a bad or transient lookup no longer permanently occupies a cache entry.
    
    Thanks: Pavel Kohout, Aisle Research for reporting.
---
 .../org/apache/ofbiz/entity/DelegatorFactory.java  |  5 +++-
 .../java/org/apache/ofbiz/webapp/WebAppUtil.java   | 27 ++++++++++++++++++++++
 .../apache/ofbiz/webapp/control/ContextFilter.java |  3 +++
 .../apache/ofbiz/webapp/control/LoginWorker.java   |  4 ++++
 4 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/framework/entity/src/main/java/org/apache/ofbiz/entity/DelegatorFactory.java b/framework/entity/src/main/java/org/apache/ofbiz/entity/DelegatorFactory.java
index c810e0798f..1bc38ed436 100644
--- a/framework/entity/src/main/java/org/apache/ofbiz/entity/DelegatorFactory.java
+++ b/framework/entity/src/main/java/org/apache/ofbiz/entity/DelegatorFactory.java
@@ -39,10 +39,13 @@ public abstract class DelegatorFactory implements Factory<Delegator, String> {
             Runtime.getRuntime().availableProcessors(), 10, true);
 
     public static Delegator getDelegator(String delegatorName) {
-        Future<Delegator> future = getDelegatorFuture(delegatorName);
+        String cacheKey = delegatorName == null ? "default" : delegatorName;
+        Future<Delegator> future = getDelegatorFuture(cacheKey);
         try {
             return future.get();
         } catch (ExecutionException | InterruptedException e) {
+            // do not let a failed lookup permanently occupy the cache under its (possibly caller-supplied) key
+            DELEGATORS.remove(cacheKey, future);
             Debug.logError(e, MODULE);
             return null;
         }
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/WebAppUtil.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/WebAppUtil.java
index 3b25858934..03b125abbe 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/WebAppUtil.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/WebAppUtil.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 import jakarta.servlet.ServletContext;
 import jakarta.servlet.ServletRequest;
@@ -41,6 +42,9 @@ import org.apache.ofbiz.base.util.UtilXml.LocalResolver;
 import org.apache.ofbiz.base.util.cache.UtilCache;
 import org.apache.ofbiz.entity.Delegator;
 import org.apache.ofbiz.entity.DelegatorFactory;
+import org.apache.ofbiz.entity.GenericEntityException;
+import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.entity.util.EntityQuery;
 import org.apache.ofbiz.security.Security;
 import org.apache.ofbiz.security.SecurityConfigurationException;
 import org.apache.ofbiz.security.SecurityFactory;
@@ -68,6 +72,8 @@ public final class WebAppUtil {
     public static final String CONTROL_MOUNT_POINT = "control";
     private static final Path WEB_APP_FILE_NAME = Paths.get("WEB-INF", "web.xml");
     private static final UtilCache<Path, WebXml> WEB_XML_CACHE = UtilCache.createUtilCache("webapp.WebXml");
+    // matches the "id" field type's VARCHAR(20) column width used by the Tenant entity's primary key
+    private static final Pattern TENANT_ID_PATTERN = Pattern.compile("[A-Za-z0-9_-]{1,20}");
 
     /**
      * Returns the control servlet path. The path consists of the web application's mount-point
@@ -222,6 +228,27 @@ public final class WebAppUtil {
         return delegator;
     }
 
+    /**
+     * Returns true if the given tenant ID belongs to an enabled <code>Tenant</code> record, as looked up through
+     * the given base (non-tenant-specific) delegator. Callers must check this before building a per-tenant
+     * delegator name from request-supplied input, since {@link DelegatorFactory} caches every delegator name it is
+     * asked for, including ones that fail to resolve.
+     * @param baseDelegator
+     * @param tenantId
+     */
+    public static boolean isValidTenantId(Delegator baseDelegator, String tenantId) {
+        if (UtilValidate.isEmpty(tenantId) || !TENANT_ID_PATTERN.matcher(tenantId).matches()) {
+            return false;
+        }
+        try {
+            GenericValue tenant = EntityQuery.use(baseDelegator).from("Tenant").where("tenantId", tenantId).queryOne();
+            return tenant != null && !"Y".equals(tenant.getString("disabled"));
+        } catch (GenericEntityException e) {
+            Debug.logError(e, "Error looking up tenant ID " + tenantId, MODULE);
+            return false;
+        }
+    }
+
     public static Security getSecurity(ServletContext servletContext) {
         Security security = (Security) servletContext.getAttribute("security");
         if (security == null) {
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ContextFilter.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ContextFilter.java
index 39672d3256..20b0303113 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ContextFilter.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ContextFilter.java
@@ -139,6 +139,9 @@ public class ContextFilter implements Filter {
                 if (UtilValidate.isEmpty(tenantId)) {
                     tenantId = httpRequest.getParameter("userTenantId");
                 }
+                if (UtilValidate.isNotEmpty(tenantId) && !WebAppUtil.isValidTenantId(baseDelegator, tenantId)) {
+                    tenantId = null;
+                }
                 if (UtilValidate.isNotEmpty(tenantId)) {
                     // if the request path is a root mount then redirect to the initial path
                     if ("".equals(httpRequest.getContextPath()) && "".equals(httpRequest.getServletPath())) {
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java
index 92ffb1c1be..457a4aef8c 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java
@@ -476,8 +476,12 @@ public final class LoginWorker {
             if (delegatorNameHashIndex == -1 || (currentDelegatorTenantId != null && !tenantId.equals(currentDelegatorTenantId))) {
                 // make that tenant active, setup a new delegator and a new dispatcher
                 String delegatorName = delegator.getDelegatorBaseName() + "#" + tenantId;
+                Delegator baseDelegator = DelegatorFactory.getDelegator(delegator.getDelegatorBaseName());
 
                 try {
+                    if (!WebAppUtil.isValidTenantId(baseDelegator, tenantId)) {
+                        throw new NullPointerException("Tenant [" + tenantId + "] not found");
+                    }
                     // after this line the delegator is replaced with the new per-tenant delegator
                     delegator = DelegatorFactory.getDelegator(delegatorName);
                     dispatcher = WebAppUtil.makeWebappDispatcher(servletContext, delegator);