This is an automated email from the ASF dual-hosted git repository.

nmalin 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 adfcce4506 Fixed: Ajax request fail on restful page (OFBIZ-13231) 
(#889)
adfcce4506 is described below

commit adfcce45063a3b7eb2a2b1bcbe4d6c6cbd60975a
Author: Nicolas Malin <nicolas.ma...@nereide.fr>
AuthorDate: Fri Jun 6 13:33:19 2025 +0200

    Fixed: Ajax request fail on restful page (OFBIZ-13231) (#889)
    
    A problem was detected with some ajax call did by js script that failed
    with error 405 like :
    
https://demo-next.ofbiz.apache.org/webtools/control/entity/find/SetTimeZoneFromBrowser
    
    Reason :
    
    SetTimeZoneFromBrowser is a request define in common-controller.xml, so
    available on all component. In js the call is realized by :
    
                $.ajax({
                    url: "SetTimeZoneFromBrowser",
                    type: "POST",
                    async: false,...
    
    Navigator use the relative url to execute the call. In general
    case we have a page like
    https://demo-next.ofbiz.apache.org/$component/control/$request , js
    script realized their call with
    https://demo-next.ofbiz.apache.org/$component/control/$request-js. Like
    each request-js are present on common-controller.xml all component that
    include it can response.
    
    With rest url, the uri pattern is more complex and the script js
    generate a relative call like we have upper :
    
_https://demo-next.ofbiz.apache.org/webtools/control/entity/find/SetTimeZoneFromBrowse_.
    The ControlServlet behind failed to retrieve the correct request and
    generate a http error 405
    
    To fix :
    
    We remove all relative js call and create a dedicated webapp for that.
    
            $.ajax({
                    url: "/common-js/control/SetTimeZoneFromBrowser",
                    type: "POST",
                    async: false,...
    
    To pass through the authentification (we implement a new webapp), we
    store a jwt token with the current userLogin after the authentification
    that will use by common-ext to confirm authentification. This cookie is
    available during all the session time.
    
    For security reason, login cookie contains a jwt token generate with le 
JWTManager ofbiz class.
---
 applications/commonext/ofbiz-component.xml         |  9 ++-
 .../webapp/common-js/WEB-INF/controller.xml        | 28 ++++++++
 .../commonext/webapp/common-js/WEB-INF/web.xml     | 84 ++++++++++++++++++++++
 .../webapp/ordermgr-js/geoAutoCompleter.js         |  6 +-
 .../webapp/partymgr/static/PartyProfileContent.js  |  2 +-
 .../org/apache/ofbiz/security/SecurityUtil.java    |  8 ++-
 .../apache/ofbiz/webapp/control/LoginWorker.java   | 68 ++++++++++++++++++
 framework/webtools/widget/GeoManagementScreens.xml |  2 +-
 .../webapp/common-theme/js/util/OfbizUtil.js       |  4 +-
 .../common-theme/js/util/miscAjaxFunctions.js      |  2 +-
 .../webapp/common-theme/js/util/setUserTimeZone.js |  2 +-
 11 files changed, 200 insertions(+), 15 deletions(-)

diff --git a/applications/commonext/ofbiz-component.xml 
b/applications/commonext/ofbiz-component.xml
index e8b1f75455..72a771baa4 100644
--- a/applications/commonext/ofbiz-component.xml
+++ b/applications/commonext/ofbiz-component.xml
@@ -40,7 +40,14 @@ under the License.
         location="webapp/ofbizsetup"
         base-permission="OFBTOOLS,SETUP"
         mount-point="/ofbizsetup"/>
-    
+
+    <webapp name="common-js"
+        title="common-js"
+        server="default-server"
+        location="webapp/common-js"
+        mount-point="/common-js"
+        app-bar-display="false"/>
+
     <webapp name="ordermgr-js"
         title="ordermgr-js"
         server="default-server"
diff --git a/applications/commonext/webapp/common-js/WEB-INF/controller.xml 
b/applications/commonext/webapp/common-js/WEB-INF/controller.xml
new file mode 100644
index 0000000000..e3d9181319
--- /dev/null
+++ b/applications/commonext/webapp/common-js/WEB-INF/controller.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+
+<site-conf xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+    xmlns="http://ofbiz.apache.org/Site-Conf"; 
xsi:schemaLocation="http://ofbiz.apache.org/Site-Conf 
http://ofbiz.apache.org/dtds/site-conf.xsd";>
+    <include 
location="component://common/webcommon/WEB-INF/common-controller.xml"/>
+
+    <preprocessor>
+        <event name="securedUserLoginByJWTCookie" type="java" 
path="org.apache.ofbiz.webapp.control.LoginWorker" 
invoke="securedUserLoginByJWTCookie"/>
+    </preprocessor>
+</site-conf>
diff --git a/applications/commonext/webapp/common-js/WEB-INF/web.xml 
b/applications/commonext/webapp/common-js/WEB-INF/web.xml
new file mode 100644
index 0000000000..22fe943683
--- /dev/null
+++ b/applications/commonext/webapp/common-js/WEB-INF/web.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+
+<web-app version="4.0" xmlns="http://xmlns.jcp.org/xml/ns/javaee"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd";>
+    <display-name>Apache OFBiz - Setup Manager</display-name>
+    <description>Common Ext Module of the Apache OFBiz Project</description>
+    
+    <context-param>
+        <description>The Name of the Entity Delegator to use, defined in 
entityengine.xml</description>
+        <param-name>entityDelegatorName</param-name>
+        <param-value>default</param-value>
+    </context-param>
+    <context-param>
+        <description>A unique name used to identify/recognize the local 
dispatcher for the Service Engine</description>
+        <param-name>localDispatcherName</param-name>
+        <param-value>common-js</param-value>
+    </context-param>
+
+    <filter>
+        <display-name>ControlFilter</display-name>
+        <filter-name>ControlFilter</filter-name>
+        
<filter-class>org.apache.ofbiz.webapp.control.ControlFilter</filter-class>
+        <init-param>
+            <param-name>allowedPaths</param-name>
+            <param-value>/error:/control:/js</param-value>
+        </init-param>
+    </filter>
+    <filter>
+        <display-name>ContextFilter</display-name>
+        <filter-name>ContextFilter</filter-name>
+        
<filter-class>org.apache.ofbiz.webapp.control.ContextFilter</filter-class>
+    </filter>
+    <filter>
+        <display-name>SameSiteFilter</display-name>
+        <filter-name>SameSiteFilter</filter-name>
+        
<filter-class>org.apache.ofbiz.webapp.control.SameSiteFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>ControlFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <filter-mapping>
+        <filter-name>ContextFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <filter-mapping>
+        <filter-name>SameSiteFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    
<listener><listener-class>org.apache.ofbiz.webapp.control.ControlEventListener</listener-class></listener>
+    
<listener><listener-class>org.apache.ofbiz.webapp.control.LoginEventListener</listener-class></listener>
+    
+    <servlet>
+        <description>Main Control Servlet</description>
+        <display-name>ControlServlet</display-name>
+        <servlet-name>ControlServlet</servlet-name>
+        
<servlet-class>org.apache.ofbiz.webapp.control.ControlServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>ControlServlet</servlet-name>
+        <url-pattern>/control/*</url-pattern>
+    </servlet-mapping>
+
+</web-app>
diff --git a/applications/commonext/webapp/ordermgr-js/geoAutoCompleter.js 
b/applications/commonext/webapp/ordermgr-js/geoAutoCompleter.js
index 6359edfcfe..6a71594e47 100644
--- a/applications/commonext/webapp/ordermgr-js/geoAutoCompleter.js
+++ b/applications/commonext/webapp/ordermgr-js/geoAutoCompleter.js
@@ -80,12 +80,8 @@ function setKeyAsParameter(event, ui) {
 //Generic function for fetching country's associated state list.
 function getAssociatedStateList(countryId, stateId, errorId, divId) {
     var countryGeoId = jQuery("#" + countryId).val();
-    var requestToSend = "getAssociatedStateList";
-    if (jQuery('#orderViewed').length) {
-        requestToSend = "/ordermgr/control/getAssociatedStateList"
-    }
     jQuery.ajax({
-        url: requestToSend,
+        url: '/common-js/control/getAssociatedStateList',
         type: "POST",
         data: {countryGeoId: countryGeoId},
         success: function(data) {
diff --git a/applications/party/webapp/partymgr/static/PartyProfileContent.js 
b/applications/party/webapp/partymgr/static/PartyProfileContent.js
index 1fb701ae95..ae7af9bdf7 100644
--- a/applications/party/webapp/partymgr/static/PartyProfileContent.js
+++ b/applications/party/webapp/partymgr/static/PartyProfileContent.js
@@ -97,7 +97,7 @@ function getUploadProgressStatus(event){
             tick: function(counter, timerId) {
                 var timerId = timerId;
                 jQuery.ajax({
-                    url: 'getFileUploadProgressStatus',
+                    url: '/common-js/control/getFileUploadProgressStatus',
                     dataType: 'json',
                     success: function(data) {
                         if (data._ERROR_MESSAGE_LIST_ != undefined) {
diff --git 
a/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java 
b/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java
index 7648ab49c6..0f6adbd14f 100644
--- 
a/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java
+++ 
b/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java
@@ -153,9 +153,11 @@ public final class SecurityUtil {
         if (UtilValidate.isNotEmpty(jwtToken)) {
             try {
                 GenericValue userLogin = 
EntityQuery.use(delegator).from("UserLogin").where("userLoginId", 
userLoginId).queryOne();
-                Map<String, Object> claims = 
JWTManager.validateToken(delegator, jwtToken,
-                        userLogin.getString("userLoginId") + 
userLogin.getString("currentPassword"));
-                return (!ServiceUtil.isError(claims)) && 
userLoginId.equals(claims.get("userLoginId"));
+                if (userLoginId != null) {
+                    Map<String, Object> claims = 
JWTManager.validateToken(delegator, jwtToken,
+                            userLogin.getString("userLoginId") + 
userLogin.getString("currentPassword"));
+                    return (!ServiceUtil.isError(claims)) && 
userLoginId.equals(claims.get("userLoginId"));
+                }
             } catch (GenericEntityException e) {
                 Debug.logWarning("failed to validate a jwToken for user " + 
userLoginId, MODULE);
             }
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 5a17e539a2..8eca85f4e0 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
@@ -18,6 +18,8 @@
  
*******************************************************************************/
 package org.apache.ofbiz.webapp.control;
 
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.interfaces.DecodedJWT;
 import static org.apache.ofbiz.base.util.UtilGenerics.checkMap;
 
 import java.math.BigInteger;
@@ -80,6 +82,7 @@ import org.apache.ofbiz.entity.util.EntityUtilProperties;
 import org.apache.ofbiz.security.Security;
 import org.apache.ofbiz.security.SecurityConfigurationException;
 import org.apache.ofbiz.security.SecurityFactory;
+import org.apache.ofbiz.security.SecurityUtil;
 import org.apache.ofbiz.service.GenericServiceException;
 import org.apache.ofbiz.service.LocalDispatcher;
 import org.apache.ofbiz.service.ModelService;
@@ -87,6 +90,7 @@ import org.apache.ofbiz.service.ServiceUtil;
 import org.apache.ofbiz.webapp.WebAppCache;
 import org.apache.ofbiz.webapp.WebAppUtil;
 import org.apache.ofbiz.webapp.stats.VisitHandler;
+import org.apache.ofbiz.webapp.website.WebSiteProperties;
 import org.apache.ofbiz.widget.model.ThemeFactory;
 
 /**
@@ -825,6 +829,9 @@ public final class LoginWorker {
         // Create a secured cookie with the correct userLoginId
         createSecuredLoginIdCookie(request, response);
 
+        // Create a secured cookie with a jwt contains the userLoginId
+        createSecuredLoginJwtCookie(request, response);
+
         // make sure the autoUserLogin is set to the same and that the client 
cookie has the correct userLoginId
         autoLoginSet(request, response);
 
@@ -1000,6 +1007,38 @@ public final class LoginWorker {
         }
     }
 
+    /**
+     * Set to response a cookie that contains an identification jwt to share 
with some other webapp who authenticate with
+     * event securedUserLoginByJWTCookie
+     * @param request
+     * @param response
+     */
+    public static void createSecuredLoginJwtCookie(HttpServletRequest request, 
HttpServletResponse response) {
+        Delegator delegator = (Delegator) request.getAttribute("delegator");
+        HttpSession session = request.getSession();
+        GenericValue userLogin = (GenericValue) 
session.getAttribute("userLogin");
+        if (userLogin != null) {
+            try {
+                String cookieName = "securedLoginToken";
+                Cookie securedLoginTokenCookie = new Cookie(cookieName,
+                        SecurityUtil.generateJwtToAuthenticateUserLogin(
+                                delegator, 
userLogin.getString("userLoginId")));
+                String cookieDomain = "";
+                try {
+                    WebSiteProperties webSiteProperties = 
WebSiteProperties.from(request);
+                    cookieDomain = webSiteProperties != null
+                            ? webSiteProperties.getHttpsHost()
+                            : EntityUtilProperties.getPropertyValue("url", 
"cookie.domain", delegator);
+                } catch (GenericEntityException ignored) { }
+                securedLoginTokenCookie.setDomain(cookieDomain);
+                securedLoginTokenCookie.setPath("/");
+                securedLoginTokenCookie.setSecure(true);
+                securedLoginTokenCookie.setHttpOnly(true);
+                response.addCookie(securedLoginTokenCookie);
+            } catch (Exception ignored) { }
+        }
+    }
+
     protected static String getAutoLoginCookieName(HttpServletRequest request) 
{
         return UtilHttp.getApplicationName(request) + ".autoUserLoginId";
     }
@@ -1040,6 +1079,27 @@ public final class LoginWorker {
         }
         return securedUserLoginId;
     }
+    public static String getSecuredUserLoginByJWT(HttpServletRequest request) {
+        Delegator delegator = (Delegator) request.getAttribute("delegator");
+        Cookie[] cookies = request.getCookies();
+        if (cookies != null) {
+            Optional<Cookie> securedCookie = Arrays.stream(cookies)
+                    .filter(cookie -> 
cookie.getName().equals("securedLoginToken"))
+                    .findFirst();
+            if (securedCookie.isPresent()) {
+                try {
+                    DecodedJWT jwt = 
JWT.decode(securedCookie.get().getValue());
+                    if (SecurityUtil.authenticateUserLoginByJWT(delegator,
+                            jwt.getClaim("userLoginId").asString(), 
jwt.getToken())) {
+                        return jwt.getClaim("userLoginId").asString();
+                    }
+                } catch (Exception failed) {
+                    Debug.logWarning(failed, MODULE);
+                }
+            }
+        }
+        return null;
+    }
 
     public static String autoLoginCheck(HttpServletRequest request, 
HttpServletResponse response) {
         Delegator delegator = (Delegator) request.getAttribute("delegator");
@@ -1051,6 +1111,14 @@ public final class LoginWorker {
         return autoLoginCheck(delegator, session, getAutoUserLoginId(request));
     }
 
+    public static String securedUserLoginByJWTCookie(HttpServletRequest 
request, HttpServletResponse response) {
+        String userLoginId = getSecuredUserLoginByJWT(request);
+        if (userLoginId != null) {
+            return loginUserWithUserLoginId(request, response, userLoginId);
+        }
+        return "success";
+    }
+
     private static String autoLoginCheck(Delegator delegator, HttpSession 
session, String autoUserLoginId) {
         if (autoUserLoginId != null) {
             if (Debug.infoOn()) {
diff --git a/framework/webtools/widget/GeoManagementScreens.xml 
b/framework/webtools/widget/GeoManagementScreens.xml
index 09fcbaa711..87d8a5cd3d 100644
--- a/framework/webtools/widget/GeoManagementScreens.xml
+++ b/framework/webtools/widget/GeoManagementScreens.xml
@@ -168,7 +168,7 @@
                 <set field="asm_title" 
from-field="uiLabelMap.WebtoolsGeosSelect"/>
                 <!-- selectMultipleRelatedValues parameters -->
                 <set field="asm_relatedField" value="LinkGeos_geoId"/>
-                <set field="asm_requestName" value="getRelatedGeos"/>
+                <set field="asm_requestName" 
value="/common-js/control/getRelatedGeos"/>
                 <set field="asm_paramKey" value="geoId"/>
                 <set field="asm_type" value="geoAssocTypeId"/>
                 <set field="asm_typeField" value="LinkGeos_geoAssocTypeId"/>
diff --git a/themes/common-theme/webapp/common-theme/js/util/OfbizUtil.js 
b/themes/common-theme/webapp/common-theme/js/util/OfbizUtil.js
index b1f15cd39a..3896ee8346 100644
--- a/themes/common-theme/webapp/common-theme/js/util/OfbizUtil.js
+++ b/themes/common-theme/webapp/common-theme/js/util/OfbizUtil.js
@@ -1437,7 +1437,7 @@ function getJSONuiLabels(requiredLabels, callback) {
 
     if (requiredLabels != null && requiredLabels != "") {
         jQuery.ajax({
-            url: "getUiLabels",
+            url: "/common-js/control/getUiLabels",
             type: "POST",
             async: false,
             data: { "requiredLabels": requiredLabelsStr, "widgetVerbose": 
false },
@@ -1547,7 +1547,7 @@ function submitPagination(obj, url) {
 function loadJWT() {
     var JwtToken = "";
     jQuery.ajax({
-        url: "loadJWT",
+        url: "/common-js/control/loadJWT",
         type: "POST",
         async: false,
         dataType: "text",
diff --git 
a/themes/common-theme/webapp/common-theme/js/util/miscAjaxFunctions.js 
b/themes/common-theme/webapp/common-theme/js/util/miscAjaxFunctions.js
index 362b26f2d1..e7fb1752dc 100644
--- a/themes/common-theme/webapp/common-theme/js/util/miscAjaxFunctions.js
+++ b/themes/common-theme/webapp/common-theme/js/util/miscAjaxFunctions.js
@@ -141,7 +141,7 @@ function getServiceResult(){
     var data;
     jQuery.ajax({
         type: 'POST',
-        url: request,
+        url: '/common-js/control/' + request,
         data: prepareAjaxData(arguments),
         async: false,
         cache: false,
diff --git a/themes/common-theme/webapp/common-theme/js/util/setUserTimeZone.js 
b/themes/common-theme/webapp/common-theme/js/util/setUserTimeZone.js
index d2ee292d7f..04baa7ac11 100644
--- a/themes/common-theme/webapp/common-theme/js/util/setUserTimeZone.js
+++ b/themes/common-theme/webapp/common-theme/js/util/setUserTimeZone.js
@@ -21,7 +21,7 @@ under the License.
 if (sessionStorage.getItem("SetTimeZoneFromBrowser") === null || 
sessionStorage.getItem("SetTimeZoneFromBrowser") !== "done") {
     const timezone = moment.tz.guess();
     $.ajax({
-        url: "SetTimeZoneFromBrowser",
+        url: "/common-js/control/SetTimeZoneFromBrowser",
         type: "POST",
         async: false,
         data: "localeName=" + timezone,

Reply via email to