Improved: moved the logic/implementation of OFBiz legacy authentication 
tokens from the LoginWorker class to a new class named ExternalLoginKeysManager.

Improved Javadocs in the new class.
 


git-svn-id: https://svn.apache.org/repos/asf/ofbiz/trunk@1775807 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/common/webcommon/WEB-INF/common-controller.xml b/framework/common/webcommon/WEB-INF/common-controller.xml
index 637c859..c6e28f0 100644
--- a/framework/common/webcommon/WEB-INF/common-controller.xml
+++ b/framework/common/webcommon/WEB-INF/common-controller.xml
@@ -30,7 +30,7 @@
         <event name="check509CertLogin" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="check509CertLogin"/>
         <event name="checkRequestHeaderLogin" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="checkRequestHeaderLogin"/>
         <event name="checkServletRequestRemoteUserLogin" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="checkServletRequestRemoteUserLogin"/>
-        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="checkExternalLoginKey"/>
+        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.ExternalLoginKeysManager" invoke="checkExternalLoginKey"/>
         <event name="checkProtectedView" type="java" path="org.apache.ofbiz.webapp.control.ProtectViewWorker" invoke="checkProtectedView"/>
         <event name="extensionConnectLogin" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="extensionConnectLogin"/>
     </preprocessor>
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ExternalLoginKeysManager.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ExternalLoginKeysManager.java
new file mode 100644
index 0000000..c308ddd
--- /dev/null
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ExternalLoginKeysManager.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+package org.apache.ofbiz.webapp.control;
+
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.entity.Delegator;
+import org.apache.ofbiz.entity.DelegatorFactory;
+import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.entity.util.EntityUtilProperties;
+import org.apache.ofbiz.service.LocalDispatcher;
+import org.apache.ofbiz.webapp.WebAppUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This class manages the authentication tokens that provide single sign-on authentication to the OFBiz applications.
+ */
+public class ExternalLoginKeysManager {
+    private static final String module = ExternalLoginKeysManager.class.getName();
+    private static final String EXTERNAL_LOGIN_KEY_ATTR = "externalLoginKey";
+    // This Map is keyed by the randomly generated externalLoginKey and the value is a UserLogin GenericValue object
+    private static final Map<String, GenericValue> externalLoginKeys = new ConcurrentHashMap<>();
+
+    /**
+     * Gets (and creates if necessary) an authentication token to be used for an external login parameter.
+     * When a new token is created, it is persisted in the web session and in the web request and map entry keyed by the
+     * token and valued by a userLogin object is added to a map that is looked up for subsequent requests.
+     *
+     * @param request - the http request in which the authentication token is searched and stored
+     * @return the authentication token as persisted in the session and request objects
+     */
+    public static String getExternalLoginKey(HttpServletRequest request) {
+        Delegator delegator = (Delegator) request.getAttribute("delegator");
+        boolean externalLoginKeyEnabled = "true".equals(EntityUtilProperties.getPropertyValue("security", "security.login.externalLoginKey.enabled", "true", delegator));
+        if (!externalLoginKeyEnabled) {
+            return null;
+        }
+        GenericValue userLogin = (GenericValue) request.getAttribute("userLogin");
+
+        String externalKey = (String) request.getAttribute(EXTERNAL_LOGIN_KEY_ATTR);
+        if (externalKey != null) return externalKey;
+
+        HttpSession session = request.getSession();
+        synchronized (session) {
+            // if the session has a previous key in place, remove it from the master list
+            String sesExtKey = (String) session.getAttribute(EXTERNAL_LOGIN_KEY_ATTR);
+
+            if (sesExtKey != null) {
+                if (isAjax(request)) return sesExtKey;
+
+                externalLoginKeys.remove(sesExtKey);
+            }
+
+            //check the userLogin here, after the old session setting is set so that it will always be cleared
+            if (userLogin == null) return "";
+
+            //no key made yet for this request, create one
+            while (externalKey == null || externalLoginKeys.containsKey(externalKey)) {
+                UUID uuid = UUID.randomUUID();
+                externalKey = "EL" + uuid.toString();
+            }
+
+            request.setAttribute(EXTERNAL_LOGIN_KEY_ATTR, externalKey);
+            session.setAttribute(EXTERNAL_LOGIN_KEY_ATTR, externalKey);
+            externalLoginKeys.put(externalKey, userLogin);
+            return externalKey;
+        }
+    }
+
+    /**
+     * Removes the authentication token, if any, from the session.
+     *
+     * @param session - the http session from which the authentication token is removed
+     */
+    static void cleanupExternalLoginKey(HttpSession session) {
+        String sesExtKey = (String) session.getAttribute(EXTERNAL_LOGIN_KEY_ATTR);
+        if (sesExtKey != null) {
+            externalLoginKeys.remove(sesExtKey);
+        }
+    }
+
+    /**
+     * OFBiz controller event that performs the user authentication using the authentication token.
+     * The methods is designed to be used in a chain of controller preprocessor event: it always return &amp;success&amp;
+     * even when the authentication token is missing or the authentication fails in order to move the processing to the
+     * next event in the chain.
+     *
+     * @param request - the http request object
+     * @param response - the http response object
+     * @return - &amp;success&amp; in all the cases
+     */
+    public static String checkExternalLoginKey(HttpServletRequest request, HttpServletResponse response) {
+        HttpSession session = request.getSession();
+
+        String externalKey = request.getParameter(EXTERNAL_LOGIN_KEY_ATTR);
+        if (externalKey == null) return "success";
+
+        GenericValue userLogin = externalLoginKeys.get(externalKey);
+        if (userLogin != null) {
+            //to check it's the right tenant
+            //in case username and password are the same in different tenants
+            Delegator delegator = (Delegator) request.getAttribute("delegator");
+            String oldDelegatorName = delegator.getDelegatorName();
+            if (!oldDelegatorName.equals(userLogin.getDelegator().getDelegatorName())) {
+                delegator = DelegatorFactory.getDelegator(userLogin.getDelegator().getDelegatorName());
+                LocalDispatcher dispatcher = WebAppUtil.makeWebappDispatcher(session.getServletContext(), delegator);
+                LoginWorker.setWebContextObjects(request, response, delegator, dispatcher);
+            }
+            // found userLogin, do the external login...
+
+            // if the user is already logged in and the login is different, logout the other user
+            GenericValue currentUserLogin = (GenericValue) session.getAttribute("userLogin");
+            if (currentUserLogin != null) {
+                if (currentUserLogin.getString("userLoginId").equals(userLogin.getString("userLoginId"))) {
+                    // is the same user, just carry on...
+                    return "success";
+                }
+
+                // logout the current user and login the new user...
+                LoginWorker.logout(request, response);
+                // ignore the return value; even if the operation failed we want to set the new UserLogin
+            }
+
+            LoginWorker.doBasicLogin(userLogin, request);
+        } else {
+            Debug.logWarning("Could not find userLogin for external login key: " + externalKey, module);
+        }
+
+        return "success";
+    }
+
+    private static boolean isAjax(HttpServletRequest request) {
+       return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
+    }
+
+}
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginEventListener.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginEventListener.java
index f2d8f6b..6579538 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginEventListener.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginEventListener.java
@@ -23,8 +23,6 @@
 import javax.servlet.http.HttpSessionEvent;
 import javax.servlet.http.HttpSessionListener;
 
-import org.apache.ofbiz.webapp.control.LoginWorker;
-
 /**
  * HttpSessionListener that finalizes login information
  */
@@ -41,6 +39,6 @@
 
     public void sessionDestroyed(HttpSessionEvent event) {
         HttpSession session = event.getSession();
-        LoginWorker.cleanupExternalLoginKey(session);
+        ExternalLoginKeysManager.cleanupExternalLoginKey(session);
     }
 }
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 601a7d3..a33d994 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
@@ -29,8 +29,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -90,13 +88,10 @@
     public final static String module = LoginWorker.class.getName();
     public static final String resourceWebapp = "SecurityextUiLabels";
 
-    public static final String EXTERNAL_LOGIN_KEY_ATTR = "externalLoginKey";
     public static final String X509_CERT_ATTR = "SSLx509Cert";
     public static final String securityProperties = "security.properties";
 
     private static final String keyValue = UtilProperties.getPropertyValue(securityProperties, "login.secret_key_string");
-    /** This Map is keyed by the randomly generated externalLoginKey and the value is a UserLogin GenericValue object */
-    private static Map<String, GenericValue> externalLoginKeys = new ConcurrentHashMap<String, GenericValue>();
 
     public static StringWrapper makeLoginUrl(PageContext pageContext) {
         return makeLoginUrl(pageContext, "checkLogin");
@@ -128,55 +123,6 @@
         return StringUtil.wrapString(loginUrl);
     }
 
-    /**
-     * Gets (and creates if necessary) a key to be used for an external login parameter
-     */
-    public static String getExternalLoginKey(HttpServletRequest request) {
-        Delegator delegator = (Delegator) request.getAttribute("delegator");
-        boolean externalLoginKeyEnabled = "true".equals(EntityUtilProperties.getPropertyValue("security", "security.login.externalLoginKey.enabled", "true", delegator));
-        if (!externalLoginKeyEnabled) {
-            return null;
-        }
-        //Debug.logInfo("Running getExternalLoginKey, externalLoginKeys.size=" + externalLoginKeys.size(), module);
-        GenericValue userLogin = (GenericValue) request.getAttribute("userLogin");
-
-        String externalKey = (String) request.getAttribute(EXTERNAL_LOGIN_KEY_ATTR);
-        if (externalKey != null) return externalKey;
-
-        HttpSession session = request.getSession();
-        synchronized (session) {
-            // if the session has a previous key in place, remove it from the master list
-            String sesExtKey = (String) session.getAttribute(EXTERNAL_LOGIN_KEY_ATTR);
-
-            if (sesExtKey != null) {
-                if (isAjax(request)) return sesExtKey;
-
-                externalLoginKeys.remove(sesExtKey);
-            }
-
-            //check the userLogin here, after the old session setting is set so that it will always be cleared
-            if (userLogin == null) return "";
-
-            //no key made yet for this request, create one
-            while (externalKey == null || externalLoginKeys.containsKey(externalKey)) {
-                UUID uuid = UUID.randomUUID();
-                externalKey = "EL" + uuid.toString();
-            }
-
-            request.setAttribute(EXTERNAL_LOGIN_KEY_ATTR, externalKey);
-            session.setAttribute(EXTERNAL_LOGIN_KEY_ATTR, externalKey);
-            externalLoginKeys.put(externalKey, userLogin);
-            return externalKey;
-        }
-    }
-
-    public static void cleanupExternalLoginKey(HttpSession session) {
-        String sesExtKey = (String) session.getAttribute(EXTERNAL_LOGIN_KEY_ATTR);
-        if (sesExtKey != null) {
-            externalLoginKeys.remove(sesExtKey);
-        }
-    }
-
     public static void setLoggedOut(String userLoginId, Delegator delegator) {
         if (UtilValidate.isEmpty(userLoginId)) {
             Debug.logWarning("Called setLogged out with empty userLoginId", module);
@@ -567,7 +513,7 @@
         }
     }
 
-    private static void setWebContextObjects(HttpServletRequest request, HttpServletResponse response, Delegator delegator, LocalDispatcher dispatcher) {
+    protected static void setWebContextObjects(HttpServletRequest request, HttpServletResponse response, Delegator delegator, LocalDispatcher dispatcher) {
         HttpSession session = request.getSession();
         // NOTE: we do NOT want to set this in the servletContext, only in the request and session
         // We also need to setup the security objects since they are dependent on the delegator
@@ -1018,46 +964,6 @@
         return count > 0;
     }
 
-    public static String checkExternalLoginKey(HttpServletRequest request, HttpServletResponse response) {
-        HttpSession session = request.getSession();
-
-        String externalKey = request.getParameter(LoginWorker.EXTERNAL_LOGIN_KEY_ATTR);
-        if (externalKey == null) return "success";
-
-        GenericValue userLogin = LoginWorker.externalLoginKeys.get(externalKey);
-        if (userLogin != null) {
-            //to check it's the right tenant
-            //in case username and password are the same in different tenants
-            Delegator delegator = (Delegator) request.getAttribute("delegator");
-            String oldDelegatorName = delegator.getDelegatorName();
-            if (!oldDelegatorName.equals(userLogin.getDelegator().getDelegatorName())) {
-                delegator = DelegatorFactory.getDelegator(userLogin.getDelegator().getDelegatorName());
-                LocalDispatcher dispatcher = WebAppUtil.makeWebappDispatcher(session.getServletContext(), delegator);
-                setWebContextObjects(request, response, delegator, dispatcher);
-            }
-            // found userLogin, do the external login...
-
-            // if the user is already logged in and the login is different, logout the other user
-            GenericValue currentUserLogin = (GenericValue) session.getAttribute("userLogin");
-            if (currentUserLogin != null) {
-                if (currentUserLogin.getString("userLoginId").equals(userLogin.getString("userLoginId"))) {
-                    // is the same user, just carry on...
-                    return "success";
-                }
-
-                // logout the current user and login the new user...
-                logout(request, response);
-                // ignore the return value; even if the operation failed we want to set the new UserLogin
-            }
-
-            doBasicLogin(userLogin, request);
-        } else {
-            Debug.logWarning("Could not find userLogin for external login key: " + externalKey, module);
-        }
-
-        return "success";
-    }
-
     public static boolean isFlaggedLoggedOut(GenericValue userLogin, Delegator delegator) {
         if ("true".equalsIgnoreCase(EntityUtilProperties.getPropertyValue("security", "login.disable.global.logout", delegator))) {
             return false;
@@ -1159,10 +1065,6 @@
         return userLoginSessionMap;
     }
 
-    public static boolean isAjax(HttpServletRequest request) {
-       return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
-    }
-
     public static String autoChangePassword(HttpServletRequest request, HttpServletResponse response) {
         Delegator delegator = (Delegator) request.getAttribute("delegator");
         String userName = request.getParameter("USERNAME");
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
index 255b98e..cb6021f 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
@@ -21,7 +21,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -51,6 +50,7 @@
 import org.apache.ofbiz.service.DispatchContext;
 import org.apache.ofbiz.service.GenericServiceException;
 import org.apache.ofbiz.service.LocalDispatcher;
+import org.apache.ofbiz.webapp.control.ExternalLoginKeysManager;
 import org.apache.ofbiz.webapp.control.LoginWorker;
 import org.apache.ofbiz.webapp.website.WebSiteWorker;
 import org.apache.ofbiz.widget.cache.GenericWidgetOutput;
@@ -257,7 +257,7 @@
         context.put("contextRoot", request.getAttribute("_CONTEXT_ROOT_"));
         context.put("serverRoot", request.getAttribute("_SERVER_ROOT_URL_"));
         context.put("checkLoginUrl", LoginWorker.makeLoginUrl(request));
-        String externalLoginKey = LoginWorker.getExternalLoginKey(request);
+        String externalLoginKey = ExternalLoginKeysManager.getExternalLoginKey(request);
         String externalKeyParam = externalLoginKey == null ? "" : "&amp;externalLoginKey=" + externalLoginKey;
         context.put("externalLoginKey", externalLoginKey);
         context.put("externalKeyParam", externalKeyParam);
diff --git a/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml b/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml
index e8691a2..d88e6e6 100644
--- a/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml
+++ b/specialpurpose/ecommerce/webapp/ecommerce/WEB-INF/controller.xml
@@ -49,7 +49,7 @@
     <!-- Events to run on every request before security (chains exempt) -->
     <preprocessor>
         <!-- This event allows affilate/distributor entry on any page -->
-        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="checkExternalLoginKey"/>
+        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.ExternalLoginKeysManager" invoke="checkExternalLoginKey"/>
         <event name="setAssociationId" type="java" path="org.apache.ofbiz.ecommerce.misc.ThirdPartyEvents" invoke="setAssociationId"/>
         <event name="checkTrackingCodeUrlParam" type="java" path="org.apache.ofbiz.marketing.tracking.TrackingCodeEvents" invoke="checkTrackingCodeUrlParam"/>
         <event name="checkPartnerTrackingCodeUrlParam" type="java" path="org.apache.ofbiz.marketing.tracking.TrackingCodeEvents" invoke="checkPartnerTrackingCodeUrlParam"/>
diff --git a/specialpurpose/solr/webapp/solr/WEB-INF/controller.xml b/specialpurpose/solr/webapp/solr/WEB-INF/controller.xml
index 91a9135..1492cd3 100644
--- a/specialpurpose/solr/webapp/solr/WEB-INF/controller.xml
+++ b/specialpurpose/solr/webapp/solr/WEB-INF/controller.xml
@@ -10,7 +10,7 @@
 
     <!-- Events to run on every request before security (chains exempt) -->
     <preprocessor>
-        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="checkExternalLoginKey" />
+        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.ExternalLoginKeysManager" invoke="checkExternalLoginKey" />
     </preprocessor>
     
     <!-- Security Mappings -->
diff --git a/specialpurpose/webpos/webapp/webpos/WEB-INF/controller.xml b/specialpurpose/webpos/webapp/webpos/WEB-INF/controller.xml
index 6d24ef9..fbb20d8 100644
--- a/specialpurpose/webpos/webapp/webpos/WEB-INF/controller.xml
+++ b/specialpurpose/webpos/webapp/webpos/WEB-INF/controller.xml
@@ -40,7 +40,7 @@
     <!-- Events to run on every request before security (chains exempt) -->
     <preprocessor>
         <!-- This event allows affilate/distributor entry on any page -->
-        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.LoginWorker" invoke="checkExternalLoginKey"/>
+        <event name="checkExternalLoginKey" type="java" path="org.apache.ofbiz.webapp.control.ExternalLoginKeysManager" invoke="checkExternalLoginKey"/>
         <event name="setAssociationId" type="java" path="org.apache.ofbiz.ecommerce.misc.ThirdPartyEvents" invoke="setAssociationId"/>
         <event name="checkTrackingCodeUrlParam" type="java" path="org.apache.ofbiz.marketing.tracking.TrackingCodeEvents" invoke="checkTrackingCodeUrlParam"/>
         <event name="checkPartnerTrackingCodeUrlParam" type="java" path="org.apache.ofbiz.marketing.tracking.TrackingCodeEvents" invoke="checkPartnerTrackingCodeUrlParam"/>