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,