Ver Fonte

- controlledpwmservlet test case
- remove Helper.java
- code refactoring

Jason Rivard há 8 anos atrás
pai
commit
d30816d12e
51 ficheiros alterados com 769 adições e 782 exclusões
  1. 6 0
      pom.xml
  2. 3 3
      src/main/java/password/pwm/PwmAboutProperty.java
  3. 16 2
      src/main/java/password/pwm/PwmApplication.java
  4. 1 1
      src/main/java/password/pwm/config/Configuration.java
  5. 2 3
      src/main/java/password/pwm/health/HealthMonitor.java
  6. 1 2
      src/main/java/password/pwm/http/ContextManager.java
  7. 1 2
      src/main/java/password/pwm/http/PwmResponse.java
  8. 99 4
      src/main/java/password/pwm/http/filter/SessionFilter.java
  9. 25 22
      src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java
  10. 30 46
      src/main/java/password/pwm/http/servlet/LoginServlet.java
  11. 3 8
      src/main/java/password/pwm/http/servlet/LogoutServlet.java
  12. 2 1
      src/main/java/password/pwm/http/servlet/PwmServletDefinition.java
  13. 3 8
      src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java
  14. 3 63
      src/main/java/password/pwm/http/servlet/UpdateProfileServlet.java
  15. 4 9
      src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  16. 1 2
      src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  17. 6 7
      src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java
  18. 5 10
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  19. 109 1
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java
  20. 112 225
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  21. 4 11
      src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  22. 1 2
      src/main/java/password/pwm/http/tag/ErrorMessageTag.java
  23. 22 2
      src/main/java/password/pwm/http/tag/PwmFormIDTag.java
  24. 1 2
      src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  25. 1 2
      src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java
  26. 2 2
      src/main/java/password/pwm/svc/cache/LocalDBCacheStore.java
  27. 1 2
      src/main/java/password/pwm/svc/event/AuditService.java
  28. 3 3
      src/main/java/password/pwm/svc/event/LocalDbAuditVault.java
  29. 1 2
      src/main/java/password/pwm/svc/intruder/IntruderManager.java
  30. 2 3
      src/main/java/password/pwm/svc/report/ReportCsvUtility.java
  31. 2 3
      src/main/java/password/pwm/svc/report/ReportService.java
  32. 2 3
      src/main/java/password/pwm/svc/stats/StatisticsManager.java
  33. 2 3
      src/main/java/password/pwm/svc/token/TokenService.java
  34. 3 4
      src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java
  35. 2 3
      src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java
  36. 0 272
      src/main/java/password/pwm/util/Helper.java
  37. 2 2
      src/main/java/password/pwm/util/cli/commands/LocalDBInfoCommand.java
  38. 1 3
      src/main/java/password/pwm/util/java/BlockingThreadPool.java
  39. 68 9
      src/main/java/password/pwm/util/java/JavaHelper.java
  40. 31 0
      src/main/java/password/pwm/util/java/StringUtil.java
  41. 5 5
      src/main/java/password/pwm/util/localdb/Derby_LocalDB.java
  42. 2 3
      src/main/java/password/pwm/util/localdb/LocalDBFactory.java
  43. 1 2
      src/main/java/password/pwm/util/localdb/LocalDBUtility.java
  44. 1 2
      src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java
  45. 6 6
      src/main/java/password/pwm/util/logging/LocalDBLogger.java
  46. 1 2
      src/main/java/password/pwm/ws/server/RestResultBean.java
  47. 2 3
      src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp
  48. 3 3
      src/main/webapp/WEB-INF/jsp/configmanager-localdb.jsp
  49. 1 0
      src/test/java/password/pwm/error/PwmErrorTest.java
  50. 160 0
      src/test/java/password/pwm/http/servlet/ControlledPwmServletTest.java
  51. 4 4
      src/test/java/password/pwm/manual/LocalDBLoggerTest.java

+ 6 - 0
pom.xml

@@ -540,6 +540,12 @@
             <version>1.58</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.9.10</version>
+            <scope>test</scope>
+        </dependency>
 
         <!-- container dependencies -->
         <dependency>

+ 3 - 3
src/main/java/password/pwm/PwmAboutProperty.java

@@ -24,10 +24,10 @@ package password.pwm;
 
 import password.pwm.config.PwmSetting;
 import password.pwm.i18n.Display;
-import password.pwm.util.Helper;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.db.DatabaseAccessor;
 import password.pwm.util.java.FileSystemUtility;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
 
@@ -160,8 +160,8 @@ public enum PwmAboutProperty {
             aboutMap.put(app_localDbLogSize,       Integer.toString(pwmApplication.getLocalDBLogger().getStoredEventCount()));
             aboutMap.put(app_localDbLogOldestTime, dateFormatForInfoBean(pwmApplication.getLocalDBLogger().getTailDate()));
 
-            aboutMap.put(app_localDbStorageSize,   Helper.formatDiskSize(FileSystemUtility.getFileDirectorySize(pwmApplication.getLocalDB().getFileLocation())));
-            aboutMap.put(app_localDbFreeSpace,     Helper.formatDiskSize(FileSystemUtility.diskSpaceRemaining(pwmApplication.getLocalDB().getFileLocation())));
+            aboutMap.put(app_localDbStorageSize,   StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(pwmApplication.getLocalDB().getFileLocation())));
+            aboutMap.put(app_localDbFreeSpace,     StringUtil.formatDiskSize(FileSystemUtility.diskSpaceRemaining(pwmApplication.getLocalDB().getFileLocation())));
         }
 
 

+ 16 - 2
src/main/java/password/pwm/PwmApplication.java

@@ -56,7 +56,6 @@ import password.pwm.svc.token.TokenService;
 import password.pwm.svc.wordlist.SeedlistManager;
 import password.pwm.svc.wordlist.SharedHistoryManager;
 import password.pwm.svc.wordlist.WordlistManager;
-import password.pwm.util.Helper;
 import password.pwm.util.PasswordData;
 import password.pwm.util.VersionChecker;
 import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
@@ -272,7 +271,7 @@ public class PwmApplication {
                 }
             };
             postInitThread.setDaemon(true);
-            postInitThread.setName(Helper.makeThreadName(this, PwmApplication.class));
+            postInitThread.setName(JavaHelper.makeThreadName(this, PwmApplication.class));
             postInitThread.start();
         }
     }
@@ -780,6 +779,21 @@ public class PwmApplication {
         }
         return tempDirectory;
     }
+
+    public boolean determineIfDetailErrorMsgShown() {
+        final PwmApplicationMode mode = this.getApplicationMode();
+        if (mode == PwmApplicationMode.CONFIGURATION || mode == PwmApplicationMode.NEW) {
+            return true;
+        }
+        if (mode == PwmApplicationMode.RUNNING) {
+            if (this.getConfig() != null) {
+                if (this.getConfig().readSettingAsBoolean(PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 }
 
 

+ 1 - 1
src/main/java/password/pwm/config/Configuration.java

@@ -59,9 +59,9 @@ import password.pwm.config.value.UserPermissionValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.java.JsonUtil;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.PasswordData;
+import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;

+ 2 - 3
src/main/java/password/pwm/health/HealthMonitor.java

@@ -26,7 +26,6 @@ import password.pwm.PwmApplication;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.PwmException;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -126,8 +125,8 @@ public class HealthMonitor implements PwmService {
         settings = HealthMonitorSettings.fromConfiguration(pwmApplication.getConfig());
 
         executorService = Executors.newSingleThreadScheduledExecutor(
-                Helper.makePwmThreadFactory(
-                        Helper.makeThreadName(pwmApplication, this.getClass()) + "-",
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName(pwmApplication, this.getClass()) + "-",
                         true
                 ));
 

+ 1 - 2
src/main/java/password/pwm/http/ContextManager.java

@@ -35,7 +35,6 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
@@ -205,7 +204,7 @@ public class ContextManager implements Serializable {
             handleStartupError("unable to initialize application: ", e);
         }
 
-        final String threadName = Helper.makeThreadName(pwmApplication, this.getClass()) + " timer";
+        final String threadName = JavaHelper.makeThreadName(pwmApplication, this.getClass()) + " timer";
         taskMaster = new Timer(threadName, true);
         taskMaster.schedule(new RestartFlagWatcher(), 1031, 1031);
 

+ 1 - 2
src/main/java/password/pwm/http/PwmResponse.java

@@ -30,7 +30,6 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -162,7 +161,7 @@ public class PwmResponse extends PwmHttpResponseWrapper {
                 LOGGER.error("unexpected error sending user to error page: " + e.toString());
             }
         } else {
-            final boolean showDetail = Helper.determineIfDetailErrorMsgShown(pwmRequest.getPwmApplication());
+            final boolean showDetail = pwmRequest.getPwmApplication().determineIfDetailErrorMsgShown();
             final String errorStatusText = showDetail
                     ? errorInformation.toDebugStr()
                     : errorInformation.toUserStr(pwmRequest.getPwmSession(),pwmRequest.getPwmApplication());

+ 99 - 4
src/main/java/password/pwm/http/filter/SessionFilter.java

@@ -28,6 +28,7 @@ import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
+import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.SessionVerificationMode;
@@ -45,7 +46,6 @@ import password.pwm.http.PwmResponse;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.util.Helper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -53,11 +53,15 @@ import password.pwm.util.logging.PwmLogger;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  * This session filter (invoked by the container through the web.xml descriptor) wraps all calls to the
@@ -69,7 +73,6 @@ import java.util.List;
  * @author Jason D. Rivard
  */
 public class SessionFilter extends AbstractPwmFilter {
-// ------------------------------ FIELDS ------------------------------
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(SessionFilter.class);
 
@@ -185,7 +188,7 @@ public class SessionFilter extends AbstractPwmFilter {
             final String forwardURL = pwmRequest.readParameterAsString(forwardURLParamName);
             if (forwardURL != null && forwardURL.length() > 0) {
                 try {
-                    Helper.checkUrlAgainstWhitelist(pwmApplication, pwmRequest.getSessionLabel(), forwardURL);
+                    checkUrlAgainstWhitelist(pwmApplication, pwmRequest.getSessionLabel(), forwardURL);
                 } catch (PwmOperationalException e) {
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
@@ -201,7 +204,7 @@ public class SessionFilter extends AbstractPwmFilter {
             final String logoutURL = pwmRequest.readParameterAsString(logoutURLParamName);
             if (logoutURL != null && logoutURL.length() > 0) {
                 try {
-                    Helper.checkUrlAgainstWhitelist(pwmApplication, pwmRequest.getSessionLabel(), logoutURL);
+                    checkUrlAgainstWhitelist(pwmApplication, pwmRequest.getSessionLabel(), logoutURL);
                 } catch (PwmOperationalException e) {
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
@@ -423,4 +426,96 @@ public class SessionFilter extends AbstractPwmFilter {
             }
         }
     }
+
+    private static void checkUrlAgainstWhitelist(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final String inputURL
+    )
+            throws PwmOperationalException
+    {
+        LOGGER.trace(sessionLabel, "beginning test of requested redirect URL: " + inputURL);
+        if (inputURL == null || inputURL.isEmpty()) {
+            return;
+        }
+
+        final URI inputURI;
+        try {
+            inputURI = URI.create(inputURL);
+        } catch (IllegalArgumentException e) {
+            LOGGER.error(sessionLabel, "unable to parse requested redirect url '" + inputURL + "', error: " + e.getMessage());
+            // dont put input uri in error response
+            final String errorMsg = "unable to parse url: " + e.getMessage();
+            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
+        }
+
+        { // check to make sure we werent handed a non-http uri.
+            final String scheme = inputURI.getScheme();
+            if (scheme != null && !scheme.isEmpty() && !scheme.equalsIgnoreCase("http") && !scheme.equals("https")) {
+                final String errorMsg = "unsupported url scheme";
+                throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
+            }
+        }
+
+        if (inputURI.getHost() != null && !inputURI.getHost().isEmpty()) { // disallow localhost uri
+            try {
+                final InetAddress inetAddress = InetAddress.getByName(inputURI.getHost());
+                if (inetAddress.isLoopbackAddress()) {
+                    final String errorMsg = "redirect to loopback host is not permitted";
+                    throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
+                }
+            } catch (UnknownHostException e) {
+                /* noop */
+            }
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        if (inputURI.getScheme() != null) {
+            sb.append(inputURI.getScheme());
+            sb.append("://");
+        }
+        if (inputURI.getHost() != null) {
+            sb.append(inputURI.getHost());
+        }
+        if (inputURI.getPort() != -1) {
+            sb.append(":");
+            sb.append(inputURI.getPort());
+        }
+        if (inputURI.getPath() != null) {
+            sb.append(inputURI.getPath());
+        }
+
+        final String testURI = sb.toString();
+        LOGGER.trace(sessionLabel, "preparing to whitelist test parsed and decoded URL: " + testURI);
+
+        final String REGEX_PREFIX = "regex:";
+        final List<String> whiteList = pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.SECURITY_REDIRECT_WHITELIST);
+        for (final String loopFragment : whiteList) {
+            if (loopFragment.startsWith(REGEX_PREFIX)) {
+                try {
+                    final String strPattern = loopFragment.substring(REGEX_PREFIX.length(), loopFragment.length());
+                    final Pattern pattern = Pattern.compile(strPattern);
+                    if (pattern.matcher(testURI).matches()) {
+                        LOGGER.debug(sessionLabel, "positive URL match for regex pattern: " + strPattern);
+                        return;
+                    } else {
+                        LOGGER.trace(sessionLabel, "negative URL match for regex pattern: " + strPattern);
+                    }
+                } catch (Exception e) {
+                    LOGGER.error(sessionLabel, "error while testing URL match for regex pattern: '" + loopFragment + "', error: " + e.getMessage());;
+                }
+
+            } else {
+                if (testURI.startsWith(loopFragment)) {
+                    LOGGER.debug(sessionLabel, "positive URL match for pattern: " + loopFragment);
+                    return;
+                } else {
+                    LOGGER.trace(sessionLabel, "negative URL match for pattern: " + loopFragment);
+                }
+            }
+        }
+
+        final String errorMsg = testURI + " is not a match for any configured redirect whitelist, see setting: " + PwmSetting.SECURITY_REDIRECT_WHITELIST.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE);
+        throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
+    }
 }

+ 25 - 22
src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java

@@ -23,6 +23,7 @@
 package password.pwm.http.servlet;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
+import password.pwm.PwmConstants;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -30,6 +31,7 @@ import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmResponse;
 import password.pwm.http.bean.PwmSessionBean;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.ServletException;
@@ -38,10 +40,7 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
 
 public abstract class ControlledPwmServlet extends AbstractPwmServlet implements PwmServlet {
 
@@ -70,6 +69,22 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         throw new IllegalStateException("unable to determine PwmServletDefinition for class " + this.getClass().getName());
     }
 
+    public abstract Class<? extends ProcessAction> getProcessActionsClass();
+
+    protected ProcessAction readProcessAction(final PwmRequest request)
+            throws PwmUnrecoverableException
+    {
+        try {
+            final String inputParameter = request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST);
+            final Class processStatusClass = getProcessActionsClass();
+            final Enum answer = JavaHelper.readEnumFromString(processStatusClass, null, inputParameter);
+            return (ProcessAction)answer;
+        } catch (Exception e) {
+            LOGGER.error("error",e);
+        }
+        return null;
+    }
+
     private ProcessStatus dispatchMethod(
             final PwmRequest pwmRequest
     )
@@ -156,9 +171,9 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         String action();
     }
 
-    private static Method discoverMethodForAction(final Class clazz, final ProcessAction action) {
+    public static Method discoverMethodForAction(final Class clazz, final ProcessAction action) {
         Method interestedMethod = null;
-        final Collection<Method> methods = getAllMethods(clazz);
+        final Collection<Method> methods = JavaHelper.getAllMethodsForClass(clazz);
         for (final Method method : methods) {
             if (method.getAnnotation(ActionHandler.class) != null) {
                 final String actionName = method.getAnnotation(ActionHandler.class).action();
@@ -171,25 +186,13 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         return interestedMethod;
     }
 
-    private static Collection<Method> getAllMethods(final Class clazz) {
-        final LinkedHashSet<Method> methods = new LinkedHashSet<>();
-
-        // add local methods;
-        methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
-
-        final Class superClass = clazz.getSuperclass();
-        if (superClass != null) {
-            methods.addAll(getAllMethods(superClass));
-        }
-
-        return Collections.unmodifiableSet(methods);
-    }
-
     protected void setLastError(final PwmRequest pwmRequest, final ErrorInformation errorInformation) throws PwmUnrecoverableException {
         final Class<? extends PwmSessionBean> beanClass = this.getServletDefinition().getPwmSessionBeanClass();
-        final PwmSessionBean pwmSessionBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, beanClass);
-        pwmSessionBean.setLastError(errorInformation);
-        pwmRequest.setResponseError(errorInformation);
+        if (beanClass != null) {
+            final PwmSessionBean pwmSessionBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, beanClass);
+            pwmSessionBean.setLastError(errorInformation);
+            pwmRequest.setResponseError(errorInformation);
+        }
     }
 
     private void examineLastError(final PwmRequest pwmRequest) throws PwmUnrecoverableException {

+ 30 - 46
src/main/java/password/pwm/http/servlet/LoginServlet.java

@@ -31,6 +31,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.JspUrl;
+import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.LoginServletBean;
@@ -39,7 +40,6 @@ import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.util.CaptchaUtility;
 import password.pwm.util.PasswordData;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.ws.server.RestResultBean;
@@ -67,8 +67,7 @@ import java.util.Map;
                 PwmConstants.URL_PREFIX_PRIVATE + "/Login"
         }
 )
-public class LoginServlet extends AbstractPwmServlet {
-// ------------------------------ FIELDS ------------------------------
+public class LoginServlet extends ControlledPwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.getLogger(LoginServlet.class.getName());
 
@@ -92,76 +91,58 @@ public class LoginServlet extends AbstractPwmServlet {
         }
     }
 
-    protected LoginServletAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return LoginServletAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return LoginServletAction.class;
     }
 
-
-    public void processAction(
-            final PwmRequest pwmRequest
-    )
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
-    {
-        final boolean passwordOnly = pwmRequest.isAuthenticated() &&
+    private boolean passwordOnly(final PwmRequest pwmRequest) {
+        return pwmRequest.isAuthenticated() &&
                 pwmRequest.getPwmSession().getLoginInfoBean().getType() == AuthenticationType.AUTH_WITHOUT_PASSWORD;
 
-        final LoginServletAction action = readProcessAction(pwmRequest);
-
-        if (action != null) {
-            switch (action) {
-                case login:
-                    processLogin(pwmRequest, passwordOnly);
-                    break;
-
-                case restLogin:
-                    processRestLogin(pwmRequest, passwordOnly);
-                    break;
-
-                case receiveUrl:
-                    processReceiveUrl(pwmRequest);
-                    break;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(action);
-            }
-
-            return;
-        }
+    }
 
+    @Override
+    protected void nextStep(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException {
+        final boolean passwordOnly = passwordOnly(pwmRequest);
         forwardToJSP(pwmRequest, passwordOnly);
     }
 
-    private void processLogin(final PwmRequest pwmRequest, final boolean passwordOnly)
+    @Override
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+        return ProcessStatus.Continue;
+    }
+
+    @ActionHandler(action = "login")
+    private ProcessStatus processLogin(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException
     {
+        final boolean passwordOnly = passwordOnly(pwmRequest);
         final Map<String,String> valueMap = pwmRequest.readParametersAsMap();
         try {
             handleLoginRequest(pwmRequest, valueMap, passwordOnly);
         } catch (PwmOperationalException e) {
             pwmRequest.setResponseError(e.getErrorInformation());
             forwardToJSP(pwmRequest, passwordOnly);
-            return;
+            return ProcessStatus.Halt;
         }
 
         // login has succeeded
         pwmRequest.sendRedirect(determinePostLoginUrl(pwmRequest));
+        return ProcessStatus.Halt;
     }
 
-    private void processRestLogin(final PwmRequest pwmRequest, final boolean passwordOnly)
+    @ActionHandler(action = "restLogin")
+    private ProcessStatus processRestLogin(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException
     {
+        final boolean passwordOnly = passwordOnly(pwmRequest);
         final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap();
 
         if (valueMap == null || valueMap.isEmpty()) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"missing json request body");
             pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
-            return;
+            return ProcessStatus.Halt;
         }
 
         try {
@@ -170,7 +151,7 @@ public class LoginServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = e.getErrorInformation();
             LOGGER.trace(pwmRequest, "returning rest login error to client: " + errorInformation.toDebugStr());
             pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
-            return;
+            return ProcessStatus.Halt;
         }
 
         pwmRequest.readParametersAsMap();
@@ -182,9 +163,11 @@ public class LoginServlet extends AbstractPwmServlet {
         restResultBean.setData(resultMap);
         LOGGER.debug(pwmRequest, "rest login succeeded");
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void processReceiveUrl(final PwmRequest pwmRequest)
+    @ActionHandler(action = "receiveUrl")
+    private ProcessStatus processReceiveUrl(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException
     {
         final String encryptedNextUrl = pwmRequest.readParameterAsString(PwmConstants.PARAM_POST_LOGIN_URL);
@@ -198,6 +181,7 @@ public class LoginServlet extends AbstractPwmServlet {
         }
 
         pwmRequest.sendRedirect(PwmServletDefinition.Login);
+        return ProcessStatus.Halt;
     }
 
     private void handleLoginRequest(

+ 3 - 8
src/main/java/password/pwm/http/servlet/LogoutServlet.java

@@ -75,16 +75,11 @@ public class LogoutServlet extends ControlledPwmServlet {
         }
     }
 
-    protected LogoutAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException {
-        try {
-            return LogoutAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return LogoutAction.class;
     }
 
-
     @ActionHandler(action = "showLogout")
     public ProcessStatus processLogoutAction(
             final PwmRequest pwmRequest

+ 2 - 1
src/main/java/password/pwm/http/servlet/PwmServletDefinition.java

@@ -27,6 +27,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.http.bean.LoginServletBean;
+import password.pwm.http.bean.NewUserBean;
 import password.pwm.http.bean.PwmSessionBean;
 import password.pwm.http.bean.UpdateProfileBean;
 import password.pwm.http.servlet.admin.AdminServlet;
@@ -74,7 +75,7 @@ public enum PwmServletDefinition {
     ConfigManager_LocalDB(ConfigManagerLocalDBServlet.class, null),
     ConfigManager_Certificates(ConfigManagerCertificatesServlet.class, null),
 
-    NewUser(NewUserServlet.class, null),
+    NewUser(NewUserServlet.class, NewUserBean.class),
     ActivateUser(password.pwm.http.servlet.ActivateUserServlet.class, null),
     ForgottenPassword(password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet.class, ForgottenPasswordBean.class),
     ForgottenUsername(password.pwm.http.servlet.ForgottenUsernameServlet.class, null),

+ 3 - 8
src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java

@@ -111,14 +111,9 @@ public class SetupResponsesServlet extends ControlledPwmServlet {
         }
     }
 
-    protected SetupResponsesAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return SetupResponsesAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return SetupResponsesAction.class;
     }
 
     private SetupResponsesBean getSetupResponseBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {

+ 3 - 63
src/main/java/password/pwm/http/servlet/UpdateProfileServlet.java

@@ -118,14 +118,9 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
         }
     }
 
-    protected UpdateProfileAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return UpdateProfileAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return UpdateProfileAction.class;
     }
 
     private static UpdateAttributesProfile getProfile(final PwmRequest pwmRequest) {
@@ -136,61 +131,6 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
         return pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, UpdateProfileBean.class);
     }
 
-    /*
-    protected void processAction(final PwmRequest pwmRequest)
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
-    {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final UpdateProfileBean updateProfileBean = getBean(pwmRequest);
-        final UpdateAttributesProfile updateAttributesProfile = getProfile(pwmRequest);
-
-        if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.UPDATE_PROFILE_ENABLE)) {
-            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "Setting " + PwmSetting.UPDATE_PROFILE_ENABLE.toMenuLocationDebug(null,null) + " is not enabled."));
-            return;
-        }
-
-        if (updateAttributesProfile == null) {
-            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_NO_PROFILE_ASSIGNED));
-            return;
-        }
-
-        final UpdateProfileAction action = readProcessAction(pwmRequest);
-        if (action != null) {
-            pwmRequest.validatePwmFormID();
-            switch(action) {
-                case updateProfile:
-                    handleUpdateRequest(pwmRequest, updateAttributesProfile, updateProfileBean);
-                    break;
-
-                case agree:
-                    handleAgreeRequest(pwmRequest, updateProfileBean);
-                    break;
-
-                case confirm:
-                    updateProfileBean.setConfirmationPassed(true);
-                    break;
-
-                case unConfirm:
-                    handleUnconfirm(updateProfileBean);
-                    break;
-
-                case validate:
-                    restValidateForm(pwmRequest, updateAttributesProfile, updateProfileBean);
-                    return;
-
-                case enterCode:
-                    handleEnterCodeRequest(pwmRequest, updateProfileBean);
-                    break;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(action);
-            }
-        }
-
-        nextStep(pwmRequest);
-    }
-    */
-
     @ActionHandler(action = "enterCode")
     ProcessStatus handleEnterCodeRequest(
             final PwmRequest pwmRequest

+ 4 - 9
src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -29,7 +29,6 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
@@ -113,16 +112,12 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         }
     }
 
-    protected ChangePasswordAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return ChangePasswordAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return ChangePasswordServlet.ChangePasswordAction.class;
     }
 
+
     @ActionHandler(action = "reset")
     ProcessStatus processResetAction(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException {
 

+ 1 - 2
src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -53,7 +53,6 @@ import password.pwm.svc.PwmService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
-import password.pwm.util.Helper;
 import password.pwm.util.LDAPPermissionCalculator;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
@@ -384,7 +383,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
     {
         pwmRequest.getPwmResponse().markAsDownload(PwmConstants.ContentTypeValue.csv, PwmConstants.DOWNLOAD_FILENAME_LDAP_PERMISSION_CSV);
 
-        final CSVPrinter csvPrinter = Helper.makeCsvPrinter(pwmRequest.getPwmResponse().getOutputStream());
+        final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(pwmRequest.getPwmResponse().getOutputStream());
         try {
 
             final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest);

+ 6 - 7
src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java

@@ -36,7 +36,6 @@ import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
 import password.pwm.ldap.LdapDebugDataGenerator;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 import password.pwm.util.LDAPPermissionCalculator;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JavaHelper;
@@ -109,7 +108,7 @@ public class DebugItemGenerator {
         final String DEBUG_FILENAME = "zipDebugGeneration.csv";
 
         final ByteArrayOutputStream debugGeneratorLogBaos = new ByteArrayOutputStream();
-        final CSVPrinter debugGeneratorLogFile = Helper.makeCsvPrinter(debugGeneratorLogBaos);
+        final CSVPrinter debugGeneratorLogFile = JavaHelper.makeCsvPrinter(debugGeneratorLogBaos);
 
         for (final Class<? extends DebugItemGenerator.Generator> serviceClass : DEBUG_ZIP_ITEM_GENERATORS) {
             try {
@@ -248,7 +247,7 @@ public class DebugItemGenerator {
         @Override
         public void outputItem(final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream) throws Exception
         {
-            final Properties outputProps = Helper.newSortedProperties();
+            final Properties outputProps = JavaHelper.newSortedProperties();
 
             // java threads
             final Map<String,String> envProps = System.getenv();
@@ -272,7 +271,7 @@ public class DebugItemGenerator {
         {
 
             final Configuration config = pwmRequest.getConfig();
-            final Properties outputProps = Helper.newSortedProperties();
+            final Properties outputProps = JavaHelper.newSortedProperties();
 
             for (final AppProperty appProperty : AppProperty.values()) {
                 outputProps.setProperty(appProperty.getKey(), config.readAppProperty(appProperty));
@@ -403,7 +402,7 @@ public class DebugItemGenerator {
             }
 
             {
-                final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
+                final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
                 {
                     final List<String> headerRow = new ArrayList<>();
                     headerRow.add("Filepath");
@@ -482,7 +481,7 @@ public class DebugItemGenerator {
             final StoredConfigurationImpl storedConfiguration = ConfigManagerServlet.readCurrentConfiguration(pwmRequest);
             final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator(storedConfiguration);
 
-            final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
+            final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
             {
                 final List<String> headerRow = new ArrayList<>();
                 headerRow.add("Attribute");
@@ -538,7 +537,7 @@ public class DebugItemGenerator {
         ) throws Exception {
 
 
-            final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
+            final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
             {
                 final List<String> headerRow = new ArrayList<>();
                 headerRow.add("Label");

+ 5 - 10
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -169,6 +169,11 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
         }
     }
 
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return ForgottenPasswordAction.class;
+    }
+
     public enum ActionChoice {
         unlock,
         resetPassword,
@@ -179,16 +184,6 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
         sms,
     }
 
-    protected ForgottenPasswordAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return ForgottenPasswordAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
-    }
-
     @Override
     public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
 

+ 109 - 1
src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java

@@ -22,16 +22,40 @@
 
 package password.pwm.http.servlet.helpdesk;
 
+import com.novell.ldapchai.ChaiUser;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.FormConfiguration;
+import password.pwm.config.FormUtility;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.HelpdeskProfile;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.i18n.Display;
+import password.pwm.ldap.LdapUserDataReader;
+import password.pwm.ldap.UserDataReader;
+import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.event.UserAuditRecord;
-
+import password.pwm.util.LocaleHelper;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroMachine;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
 import java.io.Serializable;
 import java.time.Instant;
+import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 public class HelpdeskDetailInfoBean implements Serializable {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(HelpdeskDetailInfoBean.class);
+
+
     private UserInfoBean userInfoBean = new UserInfoBean();
     private String userDisplayName;
 
@@ -44,6 +68,90 @@ public class HelpdeskDetailInfoBean implements Serializable {
     private Map<FormConfiguration, List<String>> searchDetails;
     private String passwordSetDelta;
 
+    static HelpdeskDetailInfoBean makeHelpdeskDetailInfo(
+            final PwmRequest pwmRequest,
+            final HelpdeskProfile helpdeskProfile,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
+    {
+        final Instant startTime = Instant.now();
+        LOGGER.trace(pwmRequest, "beginning to assemble detail data report for user " + userIdentity);
+        final Locale actorLocale = pwmRequest.getLocale();
+        final ChaiUser theUser = HelpdeskServlet.getChaiUser(pwmRequest, helpdeskProfile, userIdentity);
+
+        if (!theUser.isValid()) {
+            return null;
+        }
+
+        final HelpdeskDetailInfoBean detailInfo = new HelpdeskDetailInfoBean();
+        final UserInfoBean uiBean = detailInfo.getUserInfoBean();
+        final UserStatusReader userStatusReader = new UserStatusReader(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
+        userStatusReader.populateUserInfoBean(uiBean, actorLocale, userIdentity, theUser.getChaiProvider());
+
+        try {
+            detailInfo.setIntruderLocked(theUser.isPasswordLocked());
+        } catch (Exception e) {
+            LOGGER.error(pwmRequest, "unexpected error reading intruder lock status for user '" + userIdentity + "', " + e.getMessage());
+        }
+
+        try {
+            detailInfo.setAccountEnabled(theUser.isAccountEnabled());
+        } catch (Exception e) {
+            LOGGER.error(pwmRequest, "unexpected error reading account enabled status for user '" + userIdentity + "', " + e.getMessage());
+        }
+
+        try {
+            detailInfo.setAccountExpired(theUser.isAccountExpired());
+        } catch (Exception e) {
+            LOGGER.error(pwmRequest, "unexpected error reading account expired status for user '" + userIdentity + "', " + e.getMessage());
+        }
+
+        try {
+            final Date lastLoginTime = theUser.readLastLoginTime();
+            detailInfo.setLastLoginTime(lastLoginTime == null ? null : lastLoginTime.toInstant());
+        } catch (Exception e) {
+            LOGGER.error(pwmRequest, "unexpected error reading last login time for user '" + userIdentity + "', " + e.getMessage());
+        }
+
+        try {
+            detailInfo.setUserHistory(pwmRequest.getPwmApplication().getAuditManager().readUserHistory(uiBean));
+        } catch (Exception e) {
+            LOGGER.error(pwmRequest, "unexpected error reading userHistory for user '" + userIdentity + "', " + e.getMessage());
+        }
+
+        if (uiBean.getPasswordLastModifiedTime() != null) {
+            final TimeDuration passwordSetDelta = TimeDuration.fromCurrent(uiBean.getPasswordLastModifiedTime());
+            detailInfo.setPasswordSetDelta(passwordSetDelta.asLongString(pwmRequest.getLocale()));
+        } else {
+            detailInfo.setPasswordSetDelta(LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable, pwmRequest));
+        }
+
+        final UserDataReader userDataReader = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY)
+                ? LdapUserDataReader.appProxiedReader(pwmRequest.getPwmApplication(), userIdentity)
+                : LdapUserDataReader.selfProxiedReader(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), userIdentity);
+
+        {
+            final List<FormConfiguration> detailFormConfig = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_DETAIL_FORM);
+            final Map<FormConfiguration,List<String>> formData = FormUtility.populateFormMapFromLdap(detailFormConfig, pwmRequest.getPwmSession().getLabel(), userDataReader);
+            detailInfo.setSearchDetails(formData);
+        }
+
+        final String configuredDisplayName = helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_DETAIL_DISPLAY_NAME);
+        if (configuredDisplayName != null && !configuredDisplayName.isEmpty()) {
+            final MacroMachine macroMachine = new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), detailInfo.getUserInfoBean(), null, userDataReader);
+            final String displayName = macroMachine.expandMacros(configuredDisplayName);
+            detailInfo.setUserDisplayName(displayName);
+        }
+
+        final TimeDuration timeDuration = TimeDuration.fromCurrent(startTime);
+        if (pwmRequest.getConfig().isDevDebugMode()) {
+            LOGGER.trace(pwmRequest, "completed assembly of detail data report for user " + userIdentity
+                    + " in " + timeDuration.asCompactString() + ", contents: " + JsonUtil.serialize(detailInfo));
+        }
+        return detailInfo;
+    }
+
     public String getUserDisplayName() {
         return userDisplayName;
     }

+ 112 - 225
src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -38,7 +38,6 @@ import password.pwm.bean.UserInfoBean;
 import password.pwm.config.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.FormConfiguration;
-import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.HelpdeskClearResponseMode;
 import password.pwm.config.option.HelpdeskUIMode;
@@ -52,18 +51,18 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.JspUrl;
+import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.servlet.AbstractPwmServlet;
-import password.pwm.i18n.Display;
+import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapUserDataReader;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserSearchEngine;
-import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.HelpdeskAuditRecord;
@@ -71,7 +70,6 @@ import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.token.TokenService;
-import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
@@ -112,7 +110,7 @@ import java.util.Map;
                 PwmConstants.URL_PREFIX_PRIVATE + "/Helpdesk",
         }
 )
-public class HelpdeskServlet extends AbstractPwmServlet {
+public class HelpdeskServlet extends ControlledPwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(HelpdeskServlet.class);
 
@@ -145,105 +143,51 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         }
     }
 
-    protected HelpdeskAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return HelpdeskAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return HelpdeskAction.class;
+    }
+
+
+    private HelpdeskProfile getHelpdeskRProfile(final PwmRequest pwmRequest) {
+        return pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmRequest.getPwmApplication());
     }
 
-    protected void processAction(final PwmRequest pwmRequest)
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
+    @Override
+    protected void nextStep(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+        pwmRequest.setAttribute(PwmRequest.Attribute.HelpdeskVerificationEnabled, !helpdeskProfile.readRequiredVerificationMethods().isEmpty());
+        pwmRequest.forwardToJsp(JspUrl.HELPDESK_SEARCH);
+    }
+
+    @Override
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
 
         if (!pwmRequest.isAuthenticated()) {
             pwmRequest.respondWithError(PwmError.ERROR_AUTHENTICATION_REQUIRED.toInfo());
-            return;
+            return ProcessStatus.Halt;
         }
 
         if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE)) {
             pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "Setting " + PwmSetting.HELPDESK_ENABLE.toMenuLocationDebug(null,null) + " is not enabled."));
-            return;
+            return ProcessStatus.Halt;
         }
 
         final HelpdeskProfile helpdeskProfile = pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmApplication);
         if (helpdeskProfile == null) {
             pwmRequest.respondWithError(PwmError.ERROR_UNAUTHORIZED.toInfo());
-            return;
+            return ProcessStatus.Halt;
         }
 
-        final HelpdeskAction action = readProcessAction(pwmRequest);
-        if (action != null) {
-            pwmRequest.validatePwmFormID();
-
-            switch (action) {
-                case search:
-                    restSearchRequest(pwmRequest, helpdeskProfile);
-                    return;
-
-                case detail:
-                    processDetailRequest(pwmRequest, helpdeskProfile);
-                    return;
-
-                case executeAction:
-                    processExecuteActionRequest(pwmRequest, helpdeskProfile);
-                    return;
-
-                case deleteUser:
-                    restDeleteUserRequest(pwmRequest, helpdeskProfile);
-                    return;
-
-                case validateOtpCode:
-                    restValidateOtpCodeRequest(pwmRequest, helpdeskProfile);
-                    return;
-
-                case unlockIntruder:
-                    restUnlockPassword(pwmRequest, helpdeskProfile);
-                    return;
-
-                case clearOtpSecret:
-                    restClearOtpSecret(pwmRequest, helpdeskProfile);
-                    return;
-
-                case sendVerificationToken:
-                    restSendVerificationTokenRequest(pwmRequest, helpdeskProfile);
-                    return;
-
-                case verifyVerificationToken:
-                    restVerifyVerificationTokenRequest(pwmRequest);
-                    return;
-
-                case clientData:
-                    restClientData(pwmRequest, helpdeskProfile);
-                    return;
-
-                case checkVerification:
-                    restCheckVerification(pwmRequest, helpdeskProfile);
-                    return;
-
-                case showVerifications:
-                    restShowVerifications(pwmRequest);
-                    return;
-
-                case validateAttributes:
-                    restValidateAttributes(pwmRequest, helpdeskProfile);
-                    return;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(action);
-            }
-        }
-
-        pwmRequest.setAttribute(PwmRequest.Attribute.HelpdeskVerificationEnabled, !helpdeskProfile.readRequiredVerificationMethods().isEmpty());
-        pwmRequest.forwardToJsp(JspUrl.HELPDESK_SEARCH);
+        return ProcessStatus.Continue;
     }
 
-    private void restClientData(final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile)
+    @ActionHandler(action = "clientData")
+    private ProcessStatus restClientData(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final HelpdeskClientDataBean returnValues = new HelpdeskClientDataBean();
         { // search page
             final List<FormConfiguration> searchForm = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
@@ -295,20 +239,22 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         final RestResultBean restResultBean = new RestResultBean(returnValues);
         LOGGER.trace(pwmRequest, "returning clientData: " + JsonUtil.serialize(restResultBean));
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void processExecuteActionRequest(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "executeAction")
+    private ProcessStatus processExecuteActionRequest(
+            final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final String userKey = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation).get("userKey");
         if (userKey == null || userKey.length() < 1) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
-            return;
+            return ProcessStatus.Halt;
         }
         final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication());
         LOGGER.debug(pwmRequest, "received executeAction request for user " + userIdentity.toString());
@@ -328,7 +274,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             LOGGER.debug(pwmRequest, errorInformation.toDebugStr());
             final RestResultBean restResultBean = RestResultBean.fromError(errorInformation, pwmRequest);
             pwmRequest.outputJsonResult(restResultBean);
-            return;
+            return ProcessStatus.Halt;
         }
 
         // check if user should be seen by actor
@@ -364,28 +310,31 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final RestResultBean restResultBean = RestResultBean.forSuccessMessage(pwmRequest.getLocale(), pwmRequest.getConfig(), Message.Success_Action, action.getName());
 
             pwmRequest.outputJsonResult(restResultBean);
+            return ProcessStatus.Halt;
         } catch (PwmOperationalException e) {
             LOGGER.error(pwmRequest, e.getErrorInformation().toDebugStr());
             final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
             pwmRequest.outputJsonResult(restResultBean);
+            return ProcessStatus.Halt;
         }
     }
 
-    private void restDeleteUserRequest(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "deleteUser")
+    private ProcessStatus restDeleteUserRequest(
+            final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
 
         final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey.length() < 1) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
-            return;
+            return ProcessStatus.Halt;
         }
 
         final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmApplication);
@@ -415,7 +364,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
             LOGGER.debug(pwmRequest, errorMsg);
             pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
-            return;
+            return ProcessStatus.Halt;
         }
 
         // mark the event log
@@ -442,19 +391,21 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         final RestResultBean restResultBean = new RestResultBean();
         restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmSession.getSessionStateBean().getLocale(),Message.Success_Unknown,pwmApplication.getConfig()));
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void processDetailRequest(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "detail")
+    private ProcessStatus processDetailRequest(
+            final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey.length() < 1) {
             pwmRequest.respondWithError(
                     new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing"));
-            return;
+            return ProcessStatus.Halt;
         }
 
         final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication()).canonicalized(pwmRequest.getPwmApplication());
@@ -468,10 +419,9 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                 pwmRequest.getSessionLabel().getSrcHostname()
         );
         pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
-
+        return ProcessStatus.Halt;
     }
 
-
     private void processDetailRequest(
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,
@@ -503,7 +453,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             return;
         }
 
-        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
+        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
         pwmRequest.setAttribute(PwmRequest.Attribute.HelpdeskDetail, helpdeskDetailInfoBean);
 
         if (helpdeskDetailInfoBean != null && helpdeskDetailInfoBean.getUserInfoBean() != null) {
@@ -517,12 +467,13 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         pwmRequest.forwardToJsp(JspUrl.HELPDESK_DETAIL);
     }
 
-    private void restSearchRequest(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "search")
+    private ProcessStatus restSearchRequest(
+            final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap();
         final String username = valueMap.get("username");
 
@@ -532,12 +483,12 @@ public class HelpdeskServlet extends AbstractPwmServlet {
 
         if (username == null ||username.isEmpty()) {
             final HelpdeskSearchResultsBean emptyResults = new HelpdeskSearchResultsBean();
-            emptyResults.setSearchResults(new ArrayList<Map<String,Object>>());
+            emptyResults.setSearchResults(new ArrayList<>());
             emptyResults.setSizeExceeded(false);
             final RestResultBean restResultBean = new RestResultBean();
             restResultBean.setData(emptyResults);
             pwmRequest.outputJsonResult(restResultBean);
-            return;
+            return ProcessStatus.Halt;
         }
 
         final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
@@ -568,7 +519,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final RestResultBean restResultBean = RestResultBean.fromError(errorInformation, pwmRequest);
             restResultBean.setData(new ArrayList<Map<String, String>>());
             pwmRequest.outputJsonResult(restResultBean);
-            return;
+            return ProcessStatus.Halt;
         }
 
         final RestResultBean restResultBean = new RestResultBean();
@@ -577,105 +528,21 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         outputData.setSizeExceeded(sizeExceeded);
         restResultBean.setData(outputData);
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-
-
-    private static HelpdeskDetailInfoBean makeHelpdeskDetailInfo(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
-    {
-        final Instant startTime = Instant.now();
-        LOGGER.trace(pwmRequest, "beginning to assemble detail data report for user " + userIdentity);
-        final Locale actorLocale = pwmRequest.getLocale();
-        final ChaiUser theUser = getChaiUser(pwmRequest, helpdeskProfile, userIdentity);
-
-        if (!theUser.isValid()) {
-            return null;
-        }
-
-        final HelpdeskDetailInfoBean detailInfo = new HelpdeskDetailInfoBean();
-        final UserInfoBean uiBean = detailInfo.getUserInfoBean();
-        final UserStatusReader userStatusReader = new UserStatusReader(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
-        userStatusReader.populateUserInfoBean(uiBean, actorLocale, userIdentity, theUser.getChaiProvider());
-
-        try {
-            detailInfo.setIntruderLocked(theUser.isPasswordLocked());
-        } catch (Exception e) {
-            LOGGER.error(pwmRequest, "unexpected error reading intruder lock status for user '" + userIdentity + "', " + e.getMessage());
-        }
-
-        try {
-            detailInfo.setAccountEnabled(theUser.isAccountEnabled());
-        } catch (Exception e) {
-            LOGGER.error(pwmRequest, "unexpected error reading account enabled status for user '" + userIdentity + "', " + e.getMessage());
-        }
-
-        try {
-            detailInfo.setAccountExpired(theUser.isAccountExpired());
-        } catch (Exception e) {
-            LOGGER.error(pwmRequest, "unexpected error reading account expired status for user '" + userIdentity + "', " + e.getMessage());
-        }
-
-        try {
-            final Date lastLoginTime = theUser.readLastLoginTime();
-            detailInfo.setLastLoginTime(lastLoginTime == null ? null : lastLoginTime.toInstant());
-        } catch (Exception e) {
-            LOGGER.error(pwmRequest, "unexpected error reading last login time for user '" + userIdentity + "', " + e.getMessage());
-        }
-
-        try {
-            detailInfo.setUserHistory(pwmRequest.getPwmApplication().getAuditManager().readUserHistory(uiBean));
-        } catch (Exception e) {
-            LOGGER.error(pwmRequest, "unexpected error reading userHistory for user '" + userIdentity + "', " + e.getMessage());
-        }
-
-        if (uiBean.getPasswordLastModifiedTime() != null) {
-            final TimeDuration passwordSetDelta = TimeDuration.fromCurrent(uiBean.getPasswordLastModifiedTime());
-            detailInfo.setPasswordSetDelta(passwordSetDelta.asLongString(pwmRequest.getLocale()));
-        } else {
-            detailInfo.setPasswordSetDelta(LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable, pwmRequest));
-        }
-
-        final UserDataReader userDataReader = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY)
-                ? LdapUserDataReader.appProxiedReader(pwmRequest.getPwmApplication(), userIdentity)
-                : LdapUserDataReader.selfProxiedReader(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), userIdentity);
-
-        {
-            final List<FormConfiguration> detailFormConfig = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_DETAIL_FORM);
-            final Map<FormConfiguration,List<String>> formData = FormUtility.populateFormMapFromLdap(detailFormConfig, pwmRequest.getPwmSession().getLabel(), userDataReader);
-            detailInfo.setSearchDetails(formData);
-        }
-
-        final String configuredDisplayName = helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_DETAIL_DISPLAY_NAME);
-        if (configuredDisplayName != null && !configuredDisplayName.isEmpty()) {
-            final MacroMachine macroMachine = new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), detailInfo.getUserInfoBean(), null, userDataReader);
-            final String displayName = macroMachine.expandMacros(configuredDisplayName);
-            detailInfo.setUserDisplayName(displayName);
-        }
-
-        final TimeDuration timeDuration = TimeDuration.fromCurrent(startTime);
-        if (pwmRequest.getConfig().isDevDebugMode()) {
-            LOGGER.trace(pwmRequest, "completed assembly of detail data report for user " + userIdentity
-                    + " in " + timeDuration.asCompactString() + ", contents: " + JsonUtil.serialize(detailInfo));
-        }
-        return detailInfo;
-    }
-
-    private void restUnlockPassword(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "unlockIntruder")
+    private ProcessStatus restUnlockPassword(
+            final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey.length() < 1) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
             pwmRequest.respondWithError(errorInformation, false);
-            return;
+            return ProcessStatus.Halt;
         }
         final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication());
 
@@ -683,7 +550,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, "password unlock request, but helpdesk unlock is not enabled");
             LOGGER.error(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation);
-            return;
+            return ProcessStatus.Halt;
         }
 
         //clear pwm intruder setting.
@@ -718,26 +585,29 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final PwmError pwmError = PwmError.forChaiError(passwordError);
             pwmRequest.respondWithError(new ErrorInformation(pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError));
             LOGGER.trace(pwmRequest, "ChaiPasswordPolicyException was thrown while resetting password: " + e.toString());
-            return;
+            return ProcessStatus.Halt;
         } catch (ChaiOperationException e) {
             final PwmError returnMsg = PwmError.forChaiError(e.getErrorCode()) == null ? PwmError.ERROR_UNKNOWN : PwmError.forChaiError(e.getErrorCode());
             final ErrorInformation error = new ErrorInformation(returnMsg, e.getMessage());
             pwmRequest.respondWithError(error);
             LOGGER.warn(pwmRequest, "error resetting password for user '" + userIdentity.toDisplayString() + "'' " + error.toDebugStr() + ", " + e.getMessage());
-            return;
+            return ProcessStatus.Halt;
         }
 
         final RestResultBean restResultBean = new RestResultBean();
         restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmRequest.getLocale(),Message.Success_Unknown,pwmRequest.getConfig()));
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void restValidateOtpCodeRequest(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "validateOtpCode")
+    private ProcessStatus restValidateOtpCodeRequest(
+            final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+
         final Instant startTime = Instant.now();
 
         final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonUtil.deserialize(
@@ -748,7 +618,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         if (userKey == null || userKey.isEmpty()) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
             pwmRequest.respondWithError(errorInformation, false);
-            return;
+            return ProcessStatus.Halt;
         }
         final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication());
 
@@ -756,7 +626,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, "password otp verification request, but otp verify is not enabled");
             LOGGER.error(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation);
-            return;
+            return ProcessStatus.Halt;
         }
 
         final String code = helpdeskVerificationRequestBean.getCode();
@@ -811,14 +681,17 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         } catch (PwmOperationalException e) {
             pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation(), pwmRequest));
         }
+        return ProcessStatus.Halt;
     }
 
-    private void restSendVerificationTokenRequest(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "sendVerificationToken")
+    private ProcessStatus restSendVerificationTokenRequest(
+            final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+
         final Instant startTime = Instant.now();
         final Configuration config = pwmRequest.getConfig();
         final Map<String,String> bodyParams = pwmRequest.readBodyAsJsonStringMap();
@@ -844,18 +717,18 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT, "unable to determine appropriate send method, missing method parameter indication from operator");
                 LOGGER.error(pwmRequest,errorInformation);
                 pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation,pwmRequest));
-                return;
+                return ProcessStatus.Halt;
             }
         }
 
         final UserIdentity userIdentity = UserIdentity.fromKey(bodyParams.get("userKey"), pwmRequest.getPwmApplication());
 
-        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
+        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
         if (helpdeskDetailInfoBean == null) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "unable to read helpdesk detail data for specified user");
             LOGGER.error(pwmRequest,errorInformation);
             pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation,pwmRequest));
-            return;
+            return ProcessStatus.Halt;
         }
         final UserInfoBean userInfoBean = helpdeskDetailInfoBean.getUserInfoBean();
         final UserDataReader userDataReader = LdapUserDataReader.appProxiedReader(pwmRequest.getPwmApplication(), userIdentity);
@@ -899,7 +772,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         } catch (PwmException e) {
             LOGGER.error(pwmRequest, e.getErrorInformation());
             pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation(),pwmRequest));
-            return;
+            return ProcessStatus.Halt;
         }
 
         StatisticsManager.incrementStat(pwmRequest,Statistic.HELPDESK_TOKENS_SENT);
@@ -923,9 +796,11 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                 + " sent to destination(s) "
                 + destDisplayString
                 + " (" + TimeDuration.fromCurrent(startTime).asCompactString() + ")");
+        return ProcessStatus.Halt;
     }
 
-    private void restVerifyVerificationTokenRequest(
+    @ActionHandler(action = "verifyVerificationToken")
+    private ProcessStatus restVerifyVerificationTokenRequest(
             final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException, ServletException
@@ -995,14 +870,17 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         final HelpdeskVerificationResponseBean responseBean = new HelpdeskVerificationResponseBean(passed, verificationStateBean.toClientString(pwmRequest.getPwmApplication()));
         final RestResultBean restResultBean = new RestResultBean(responseBean);
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void restClearOtpSecret(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile
+    @ActionHandler(action = "clearOtpSecret")
+    private ProcessStatus restClearOtpSecret(
+            final PwmRequest pwmRequest
     )
             throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+
         final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
         final UserIdentity userIdentity = userIdentityFromMap(pwmRequest, bodyMap);
 
@@ -1011,7 +889,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg);
             LOGGER.error(pwmRequest, errorMsg);
             pwmRequest.respondWithError(errorInformation);
-            return;
+            return ProcessStatus.Halt;
         }
 
         //clear pwm intruder setting.
@@ -1038,12 +916,13 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             final ErrorInformation error = new ErrorInformation(returnMsg, e.getMessage());
             pwmRequest.respondWithError(error);
             LOGGER.warn(pwmRequest, "error clearing OTP secret for user '" + userIdentity + "'' " + error.toDebugStr() + ", " + e.getMessage());
-            return;
+            return ProcessStatus.Halt;
         }
 
         final RestResultBean restResultBean = new RestResultBean();
         restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmRequest.getLocale(),Message.Success_Unknown,pwmRequest.getConfig()));
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
     private static String getSearchFilter(final Configuration configuration, final HelpdeskProfile helpdeskProfile) {
@@ -1072,7 +951,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
     }
 
 
-    private static ChaiUser getChaiUser(
+    static ChaiUser getChaiUser(
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,
             final UserIdentity userIdentity
@@ -1103,8 +982,11 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         }
     }
 
-    private void restCheckVerification(final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile)
+    @ActionHandler(action = "checkVerification")
+    private ProcessStatus restCheckVerification(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException, ServletException {
+
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
 
         final UserIdentity userIdentity = userIdentityFromMap(pwmRequest, bodyMap);
@@ -1117,6 +999,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         results.put("passed",passed);
         final RestResultBean restResultBean = new RestResultBean(results);
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
     private boolean checkIfRequiredVerificationPassed(final UserIdentity userIdentity, final HelpdeskVerificationStateBean verificationStateBean, final HelpdeskProfile helpdeskProfile) {
@@ -1132,7 +1015,8 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         return false;
     }
 
-    private void restShowVerifications(final PwmRequest pwmRequest)
+    @ActionHandler(action = "showVerifications")
+    private ProcessStatus restShowVerifications(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
     {
         final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
@@ -1146,6 +1030,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         }
         final RestResultBean restResultBean = new RestResultBean(results);
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
     private UserIdentity userIdentityFromMap(final PwmRequest pwmRequest, final Map<String,String> bodyMap) throws PwmUnrecoverableException {
@@ -1158,9 +1043,11 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         return UserIdentity.fromObfuscatedKey(userKey, pwmRequest.getPwmApplication());
     }
 
-    private void restValidateAttributes(final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile)
+    @ActionHandler(action = "validateAttributes")
+    private ProcessStatus restValidateAttributes(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException, ServletException
     {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
         final Instant startTime = Instant.now();
         final String bodyString = pwmRequest.readRequestBodyAsString();
         final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonUtil.deserialize(
@@ -1239,6 +1126,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         final HelpdeskVerificationResponseBean responseBean = new HelpdeskVerificationResponseBean(passed, verificationStateBean.toClientString(pwmRequest.getPwmApplication()));
         final RestResultBean restResultBean = new RestResultBean(responseBean);
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
     private static void sendUnlockNoticeEmail(
@@ -1257,7 +1145,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             return;
         }
 
-        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
+        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
         final MacroMachine macroMachine = new MacroMachine(
                 pwmApplication,
                 pwmRequest.getSessionLabel(),
@@ -1272,5 +1160,4 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                 macroMachine
         );
     }
-
 }

+ 4 - 11
src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -120,14 +120,9 @@ public class NewUserServlet extends ControlledPwmServlet {
         }
     }
 
-    protected NewUserAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return NewUserAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return NewUserAction.class;
     }
 
 
@@ -135,8 +130,6 @@ public class NewUserServlet extends ControlledPwmServlet {
         return pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, NewUserBean.class);
     }
 
-
-
     @Override
     public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -154,7 +147,7 @@ public class NewUserServlet extends ControlledPwmServlet {
             return ProcessStatus.Halt;
         }
 
-        final NewUserAction action = this.readProcessAction(pwmRequest);
+        final ProcessAction action = this.readProcessAction(pwmRequest);
 
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
         if (action == null) {

+ 1 - 2
src/main/java/password/pwm/http/tag/ErrorMessageTag.java

@@ -29,7 +29,6 @@ import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
 import password.pwm.http.PwmRequest;
-import password.pwm.util.Helper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -64,7 +63,7 @@ public class ErrorMessageTag extends PwmAbstractTag {
             final ErrorInformation error = (ErrorInformation)pwmRequest.getAttribute(PwmRequest.Attribute.PwmErrorInfo);
 
             if (error != null) {
-                final boolean showErrorDetail = Helper.determineIfDetailErrorMsgShown(pwmApplication);
+                final boolean showErrorDetail = pwmApplication.determineIfDetailErrorMsgShown();
 
                 String outputMsg;
                 if (showErrorDetail) {

+ 22 - 2
src/main/java/password/pwm/http/tag/PwmFormIDTag.java

@@ -22,21 +22,41 @@
 
 package password.pwm.http.tag;
 
+import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
+import password.pwm.bean.FormNonce;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.JspUtility;
 import password.pwm.http.PwmRequest;
-import password.pwm.util.Helper;
+import password.pwm.http.state.SessionStateService;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.jsp.tagext.TagSupport;
 import java.io.IOException;
+import java.time.Instant;
 
 public class PwmFormIDTag extends TagSupport {
 // --------------------- Interface Tag ---------------------
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmFormIDTag.class);
 
+    private static String buildPwmFormID(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        if (pwmApplication == null) {
+            return "";
+        }
+        final SessionStateService sessionStateService = pwmApplication.getSessionStateService();
+        final String value = sessionStateService.getSessionStateInfo(pwmRequest);
+        final FormNonce formID = new FormNonce(
+                pwmRequest.getPwmSession().getLoginInfoBean().getGuid(),
+                Instant.now(),
+                pwmRequest.getPwmSession().getLoginInfoBean().getReqCounter(),
+                value
+        );
+        return pwmRequest.getPwmApplication().getSecureService().encryptObjectToString(formID);
+    }
+
     public int doEndTag()
             throws javax.servlet.jsp.JspTagException
     {
@@ -46,7 +66,7 @@ public class PwmFormIDTag extends TagSupport {
 
         try {
             final PwmRequest pwmRequest = JspUtility.getPwmRequest(pageContext);
-            final String pwmFormID = Helper.buildPwmFormID(pwmRequest);
+            final String pwmFormID = buildPwmFormID(pwmRequest);
 
             pageContext.getOut().write(pwmFormID);
         } catch (Exception e) {

+ 1 - 2
src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -37,7 +37,6 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
 import password.pwm.http.servlet.peoplesearch.PeopleSearchConfiguration;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 
 public enum PwmIfTest {
     authenticated(new AuthenticatedTest()),
@@ -269,7 +268,7 @@ public enum PwmIfTest {
         )
                 throws ChaiUnavailableException, PwmUnrecoverableException
         {
-            return Helper.determineIfDetailErrorMsgShown(pwmRequest.getPwmApplication());
+            return pwmRequest.getPwmApplication().determineIfDetailErrorMsgShown();
         }
     }
 

+ 1 - 2
src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java

@@ -50,7 +50,6 @@ import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.util.Helper;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
@@ -108,7 +107,7 @@ public class SessionAuthenticator {
             postFailureSequence(e, username, userIdentity);
 
             if (readHiddenErrorTypes().contains(e.getError())) {
-                if (Helper.determineIfDetailErrorMsgShown(pwmApplication)) {
+                if (pwmApplication.determineIfDetailErrorMsgShown()) {
                     LOGGER.debug(pwmSession, "allowing error " + e.getError() + " to be returned though it is configured as a hidden type; "
                             + "app is currently permitting detailed error messages");
                 } else {

+ 2 - 2
src/main/java/password/pwm/svc/cache/LocalDBCacheStore.java

@@ -24,7 +24,7 @@ package password.pwm.svc.cache;
 
 import password.pwm.PwmApplication;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.Helper;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
@@ -60,7 +60,7 @@ public class LocalDBCacheStore implements CacheStore {
         } catch (LocalDBException e) {
             LOGGER.error("error while clearing LocalDB CACHE DB during init: " + e.getMessage());
         }
-        timer = new Timer(Helper.makeThreadName(pwmApplication,LocalDBCacheStore.class),true);
+        timer = new Timer(JavaHelper.makeThreadName(pwmApplication,LocalDBCacheStore.class),true);
     }
 
     @Override

+ 1 - 2
src/main/java/password/pwm/svc/event/AuditService.java

@@ -44,7 +44,6 @@ import password.pwm.health.HealthStatus;
 import password.pwm.health.HealthTopic;
 import password.pwm.http.PwmSession;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
@@ -358,7 +357,7 @@ public class AuditService implements PwmService {
     {
         final Configuration config = null;
 
-        final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
+        final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
 
         csvPrinter.printComment(" " + PwmConstants.PWM_APP_NAME + " audit record output ");
         csvPrinter.printComment(" " + JavaHelper.toIsoDate(Instant.now()));

+ 3 - 3
src/main/java/password/pwm/svc/event/LocalDbAuditVault.java

@@ -26,8 +26,8 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.error.PwmException;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 import password.pwm.util.TransactionSizeCalculator;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.Percent;
 import password.pwm.util.java.TimeDuration;
@@ -76,8 +76,8 @@ public class LocalDbAuditVault implements AuditVault {
         readOldestRecord();
 
         executorService = Executors.newSingleThreadScheduledExecutor(
-                Helper.makePwmThreadFactory(
-                        Helper.makeThreadName(pwmApplication,this.getClass()) + "-",
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName(pwmApplication,this.getClass()) + "-",
                         true
                 ));
 

+ 1 - 2
src/main/java/password/pwm/svc/intruder/IntruderManager.java

@@ -54,7 +54,6 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.DataStore;
 import password.pwm.util.DataStoreFactory;
-import password.pwm.util.Helper;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.db.DatabaseDataStore;
 import password.pwm.util.db.DatabaseTable;
@@ -165,7 +164,7 @@ public class IntruderManager implements Serializable, PwmService {
         final RecordStore recordStore;
         {
             recordStore = new DataStoreRecordStore(dataStore, this);
-            final String threadName = Helper.makeThreadName(pwmApplication, this.getClass()) + " timer";
+            final String threadName = JavaHelper.makeThreadName(pwmApplication, this.getClass()) + " timer";
             timer = new Timer(threadName, true);
             final long maxRecordAge = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.INTRUDER_RETENTION_TIME_MS));
             final long cleanerRunFrequency = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.INTRUDER_CLEANUP_FREQUENCY_MS));

+ 2 - 3
src/main/java/password/pwm/svc/report/ReportCsvUtility.java

@@ -30,7 +30,6 @@ import password.pwm.config.Configuration;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Display;
-import password.pwm.util.Helper;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.java.ClosableIterator;
 import password.pwm.util.java.JavaHelper;
@@ -54,7 +53,7 @@ public class ReportCsvUtility {
     public void outputSummaryToCsv(final OutputStream outputStream, final Locale locale)
             throws IOException {
         final List<ReportSummaryData.PresentationRow> outputList = reportService.getSummaryData().asPresentableCollection(pwmApplication.getConfig(), locale);
-        final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
+        final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
 
         for (final ReportSummaryData.PresentationRow presentationRow : outputList) {
             final List<String> headerRow = new ArrayList<>();
@@ -83,7 +82,7 @@ public class ReportCsvUtility {
 
     public void outputToCsv(final OutputStream outputStream, final boolean includeHeader, final Locale locale, final Configuration config, final ReportColumnFilter columnFilter)
             throws IOException, ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException {
-        final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
+        final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
         final Class localeClass = password.pwm.i18n.Admin.class;
         if (includeHeader) {
             final List<String> headerRow = new ArrayList<>();

+ 2 - 3
src/main/java/password/pwm/svc/report/ReportService.java

@@ -43,7 +43,6 @@ import password.pwm.ldap.UserSearchEngine;
 import password.pwm.ldap.UserStatusReader;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.stats.EventRateMeter;
-import password.pwm.util.Helper;
 import password.pwm.util.TransactionSizeCalculator;
 import password.pwm.util.java.BlockingThreadPool;
 import password.pwm.util.java.ClosableIterator;
@@ -140,8 +139,8 @@ public class ReportService implements PwmService {
         dnQueue = LocalDBStoredQueue.createLocalDBStoredQueue(pwmApplication, pwmApplication.getLocalDB(), LocalDB.DB.REPORT_QUEUE);
 
         executorService = Executors.newSingleThreadScheduledExecutor(
-                Helper.makePwmThreadFactory(
-                        Helper.makeThreadName(pwmApplication,this.getClass()) + "-",
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName(pwmApplication,this.getClass()) + "-",
                         true
                 ));
 

+ 2 - 3
src/main/java/password/pwm/svc/stats/StatisticsManager.java

@@ -41,7 +41,6 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.svc.PwmService;
 import password.pwm.util.AlertHandler;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
@@ -298,7 +297,7 @@ public class StatisticsManager implements PwmService {
         localDB.put(LocalDB.DB.PWM_STATS, DB_KEY_INITIAL_DAILY_KEY, initialDailyKey.toString());
 
         { // setup a timer to roll over at 0 Zula and one to write current stats every 10 seconds
-            final String threadName = Helper.makeThreadName(pwmApplication, this.getClass()) + " timer";
+            final String threadName = JavaHelper.makeThreadName(pwmApplication, this.getClass()) + " timer";
             daemonTimer = new Timer(threadName, true);
             daemonTimer.schedule(new FlushTask(), 10 * 1000, DB_WRITE_FREQUENCY_MS);
             daemonTimer.schedule(new NightlyTask(), Date.from(JavaHelper.nextZuluZeroTime()));
@@ -555,7 +554,7 @@ public class StatisticsManager implements PwmService {
         final Instant startTime = Instant.now();
 
         final StatisticsManager statsManger = pwmApplication.getStatisticsManager();
-        final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream);
+        final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(outputStream);
 
         if (includeHeader) {
             final List<String> headers = new ArrayList<>();

+ 2 - 3
src/main/java/password/pwm/svc/token/TokenService.java

@@ -54,7 +54,6 @@ import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
@@ -188,8 +187,8 @@ public class TokenService implements PwmService {
         }
 
         executorService = Executors.newSingleThreadScheduledExecutor(
-                Helper.makePwmThreadFactory(
-                        Helper.makeThreadName(pwmApplication, this.getClass()) + "-",
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName(pwmApplication, this.getClass()) + "-",
                         true
                 ));
 

+ 3 - 4
src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -41,7 +41,6 @@ import password.pwm.http.ContextManager;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.http.client.PwmHttpClientConfiguration;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
@@ -104,8 +103,8 @@ abstract class AbstractWordlist implements Wordlist, PwmService {
         }
 
         executorService = Executors.newSingleThreadScheduledExecutor(
-                Helper.makePwmThreadFactory(
-                        Helper.makeThreadName(pwmApplication,this.getClass()) + "-",
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName(pwmApplication,this.getClass()) + "-",
                         true
                 ));
     }
@@ -355,7 +354,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService {
                         }
                         populator = null;
                     }
-                }, Helper.makeThreadName(pwmApplication, WordlistManager.class));
+                }, JavaHelper.makeThreadName(pwmApplication, WordlistManager.class));
                 t.setDaemon(true);
                 t.start();
             }

+ 2 - 3
src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java

@@ -31,7 +31,6 @@ import password.pwm.error.PwmException;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmSession;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 import password.pwm.util.java.Sleeper;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.JavaHelper;
@@ -217,7 +216,7 @@ public class SharedHistoryManager implements PwmService {
             frequencyMs = frequencyMs < MIN_CLEANER_FREQUENCY ? MIN_CLEANER_FREQUENCY : frequencyMs;
 
             LOGGER.debug("scheduling cleaner task to run once every " + new TimeDuration(frequencyMs).asCompactString());
-            final String threadName = Helper.makeThreadName(pwmApplication, this.getClass()) + " timer";
+            final String threadName = JavaHelper.makeThreadName(pwmApplication, this.getClass()) + " timer";
             cleanerTimer = new Timer(threadName, true);
             cleanerTimer.schedule(new CleanerTask(), 1000, frequencyMs);
         }
@@ -417,7 +416,7 @@ public class SharedHistoryManager implements PwmService {
                 LOGGER.debug("starting up in background thread");
                 init(pwmApplication, settings.maxAgeMs);
             }
-        }, Helper.makeThreadName(pwmApplication, this.getClass()) + " initializer").start();
+        }, JavaHelper.makeThreadName(pwmApplication, this.getClass()) + " initializer").start();
     }
 
     private static class Settings {

+ 0 - 272
src/main/java/password/pwm/util/Helper.java

@@ -1,272 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2016 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-package password.pwm.util;
-
-import org.apache.commons.csv.CSVPrinter;
-import password.pwm.PwmApplication;
-import password.pwm.PwmApplicationMode;
-import password.pwm.PwmConstants;
-import password.pwm.bean.FormNonce;
-import password.pwm.bean.SessionLabel;
-import password.pwm.config.PwmSetting;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmOperationalException;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.PwmRequest;
-import password.pwm.http.state.SessionStateService;
-import password.pwm.util.logging.PwmLogger;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.UnknownHostException;
-import java.text.NumberFormat;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Properties;
-import java.util.TreeSet;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.regex.Pattern;
-
-/**
- * A collection of static methods used throughout PWM
- *
- * @author Jason D. Rivard
- */
-public class Helper {
-// ------------------------------ FIELDS ------------------------------
-
-    private static final PwmLogger LOGGER = PwmLogger.forClass(Helper.class);
-
-    // -------------------------- STATIC METHODS --------------------------
-
-    private Helper() {
-    }
-
-
-    public static String formatDiskSize(final long diskSize) {
-        final float COUNT = 1000;
-        if (diskSize < 1) {
-            return "n/a";
-        }
-
-        if (diskSize == 0) {
-            return "0";
-        }
-
-        final NumberFormat nf = NumberFormat.getInstance();
-        nf.setMaximumFractionDigits(2);
-
-        if (diskSize > COUNT * COUNT * COUNT) {
-            final StringBuilder sb = new StringBuilder();
-            sb.append(nf.format(diskSize / COUNT / COUNT / COUNT));
-            sb.append(" GB");
-            return sb.toString();
-        }
-
-        if (diskSize > COUNT * COUNT) {
-            final StringBuilder sb = new StringBuilder();
-            sb.append(nf.format(diskSize / COUNT / COUNT));
-            sb.append(" MB");
-            return sb.toString();
-        }
-
-        return NumberFormat.getInstance().format(diskSize) + " bytes";
-    }
-
-
-    public static String buildPwmFormID(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        if (pwmApplication == null) {
-            return "";
-        }
-        final SessionStateService sessionStateService = pwmApplication.getSessionStateService();
-        final String value = sessionStateService.getSessionStateInfo(pwmRequest);
-        final FormNonce formID = new FormNonce(
-                pwmRequest.getPwmSession().getLoginInfoBean().getGuid(),
-                Instant.now(),
-                pwmRequest.getPwmSession().getLoginInfoBean().getReqCounter(),
-                value
-        );
-        return pwmRequest.getPwmApplication().getSecureService().encryptObjectToString(formID);
-    }
-
-
-    public static String makeThreadName(final PwmApplication pwmApplication, final Class theClass) {
-        String instanceName = "-";
-        if (pwmApplication != null && pwmApplication.getInstanceID() != null) {
-            instanceName = pwmApplication.getInstanceID();
-        }
-
-        return PwmConstants.PWM_APP_NAME + "-" + instanceName + "-" + theClass.getSimpleName();
-    }
-
-    public static void checkUrlAgainstWhitelist(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final String inputURL
-    )
-            throws PwmOperationalException
-    {
-        LOGGER.trace(sessionLabel, "beginning test of requested redirect URL: " + inputURL);
-        if (inputURL == null || inputURL.isEmpty()) {
-            return;
-        }
-
-        final URI inputURI;
-        try {
-            inputURI = URI.create(inputURL);
-        } catch (IllegalArgumentException e) {
-            LOGGER.error(sessionLabel, "unable to parse requested redirect url '" + inputURL + "', error: " + e.getMessage());
-            // dont put input uri in error response
-            final String errorMsg = "unable to parse url: " + e.getMessage();
-            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
-        }
-
-        { // check to make sure we werent handed a non-http uri.
-            final String scheme = inputURI.getScheme();
-            if (scheme != null && !scheme.isEmpty() && !scheme.equalsIgnoreCase("http") && !scheme.equals("https")) {
-                final String errorMsg = "unsupported url scheme";
-                throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
-            }
-        }
-
-        if (inputURI.getHost() != null && !inputURI.getHost().isEmpty()) { // disallow localhost uri
-            try {
-                final InetAddress inetAddress = InetAddress.getByName(inputURI.getHost());
-                if (inetAddress.isLoopbackAddress()) {
-                    final String errorMsg = "redirect to loopback host is not permitted";
-                    throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
-                }
-            } catch (UnknownHostException e) {
-                /* noop */
-            }
-        }
-
-        final StringBuilder sb = new StringBuilder();
-        if (inputURI.getScheme() != null) {
-            sb.append(inputURI.getScheme());
-            sb.append("://");
-        }
-        if (inputURI.getHost() != null) {
-            sb.append(inputURI.getHost());
-        }
-        if (inputURI.getPort() != -1) {
-            sb.append(":");
-            sb.append(inputURI.getPort());
-        }
-        if (inputURI.getPath() != null) {
-            sb.append(inputURI.getPath());
-        }
-
-        final String testURI = sb.toString();
-        LOGGER.trace(sessionLabel, "preparing to whitelist test parsed and decoded URL: " + testURI);
-
-        final String REGEX_PREFIX = "regex:";
-        final List<String> whiteList = pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.SECURITY_REDIRECT_WHITELIST);
-        for (final String loopFragment : whiteList) {
-            if (loopFragment.startsWith(REGEX_PREFIX)) {
-                try {
-                    final String strPattern = loopFragment.substring(REGEX_PREFIX.length(), loopFragment.length());
-                    final Pattern pattern = Pattern.compile(strPattern);
-                    if (pattern.matcher(testURI).matches()) {
-                        LOGGER.debug(sessionLabel, "positive URL match for regex pattern: " + strPattern);
-                        return;
-                    } else {
-                        LOGGER.trace(sessionLabel, "negative URL match for regex pattern: " + strPattern);
-                    }
-                } catch (Exception e) {
-                    LOGGER.error(sessionLabel, "error while testing URL match for regex pattern: '" + loopFragment + "', error: " + e.getMessage());;
-                }
-
-            } else {
-                if (testURI.startsWith(loopFragment)) {
-                    LOGGER.debug(sessionLabel, "positive URL match for pattern: " + loopFragment);
-                    return;
-                } else {
-                    LOGGER.trace(sessionLabel, "negative URL match for pattern: " + loopFragment);
-                }
-            }
-        }
-
-        final String errorMsg = testURI + " is not a match for any configured redirect whitelist, see setting: " + PwmSetting.SECURITY_REDIRECT_WHITELIST.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE);
-        throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_REDIRECT_ILLEGAL,errorMsg));
-    }
-
-    public static boolean determineIfDetailErrorMsgShown(final PwmApplication pwmApplication) {
-        if (pwmApplication == null) {
-            return false;
-        }
-        final PwmApplicationMode mode = pwmApplication.getApplicationMode();
-        if (mode == PwmApplicationMode.CONFIGURATION || mode == PwmApplicationMode.NEW) {
-            return true;
-        }
-        if (mode == PwmApplicationMode.RUNNING) {
-            if (pwmApplication.getConfig() != null) {
-                if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    public static CSVPrinter makeCsvPrinter(final OutputStream outputStream)
-            throws IOException
-    {
-        return new CSVPrinter(new OutputStreamWriter(outputStream,PwmConstants.DEFAULT_CHARSET), PwmConstants.DEFAULT_CSV_FORMAT);
-    }
-
-
-    public static Properties newSortedProperties() {
-        return new Properties() {
-            public synchronized Enumeration<Object> keys() {
-                return Collections.enumeration(new TreeSet<>(super.keySet()));
-            }
-        };
-    }
-
-    public static ThreadFactory makePwmThreadFactory(final String namePrefix, final boolean daemon) {
-        return new ThreadFactory() {
-            private final ThreadFactory realThreadFactory = Executors.defaultThreadFactory();
-
-            @Override
-            public Thread newThread(final Runnable r) {
-                final Thread t = realThreadFactory.newThread(r);
-                t.setDaemon(daemon);
-                if (namePrefix != null) {
-                    final String newName = namePrefix + t.getName();
-                    t.setName(newName);
-                }
-                return t;
-            }
-        };
-    }
-
-}

+ 2 - 2
src/main/java/password/pwm/util/cli/commands/LocalDBInfoCommand.java

@@ -22,10 +22,10 @@
 
 package password.pwm.util.cli.commands;
 
-import password.pwm.util.Helper;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBUtility;
@@ -40,7 +40,7 @@ public class LocalDBInfoCommand extends AbstractCliCommand {
         final LocalDB localDB = cliEnvironment.getLocalDB();
         final long localDBdiskSpace = FileSystemUtility.getFileDirectorySize(localDB.getFileLocation());
         out("beginning LocalDBInfo");
-        out("LocalDB total disk space = " + NumberFormat.getInstance().format(localDBdiskSpace) + " (" + Helper.formatDiskSize(localDBdiskSpace) + ")");
+        out("LocalDB total disk space = " + NumberFormat.getInstance().format(localDBdiskSpace) + " (" + StringUtil.formatDiskSize(localDBdiskSpace) + ")");
         out("examining LocalDB, this may take a while.... ");
         for (final LocalDB.DB db : LocalDB.DB.values()) {
             out("---" + db.toString() + "---");

+ 1 - 3
src/main/java/password/pwm/util/java/BlockingThreadPool.java

@@ -22,8 +22,6 @@
 
 package password.pwm.util.java;
 
-import password.pwm.util.Helper;
-
 import java.util.concurrent.Future;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.Semaphore;
@@ -35,7 +33,7 @@ public class BlockingThreadPool extends ThreadPoolExecutor {
     private final Semaphore semaphore;
 
     public BlockingThreadPool(final int bound, final String name) {
-        super(bound, bound, 0, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(), Helper.makePwmThreadFactory(name, true));
+        super(bound, bound, 0, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(), JavaHelper.makePwmThreadFactory(name, true));
         semaphore = new Semaphore(bound);
     }
 

+ 68 - 9
src/main/java/password/pwm/util/java/JavaHelper.java

@@ -22,16 +22,18 @@
 
 package password.pwm.util.java;
 
+import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.io.IOUtils;
+import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.time.Instant;
 import java.util.ArrayList;
@@ -40,10 +42,16 @@ import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Enumeration;
 import java.util.GregorianCalendar;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Properties;
 import java.util.TimeZone;
+import java.util.TreeSet;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
@@ -150,18 +158,16 @@ public class JavaHelper {
     }
 
     public static <E extends Enum<E>> E readEnumFromString(final Class<E> enumClass, final E defaultValue, final String input) {
-        if (input == null) {
+        if (StringUtil.isEmpty(input)) {
+            return defaultValue;
+        }
+
+        if (enumClass == null || !enumClass.isEnum()) {
             return defaultValue;
         }
 
         try {
-            final Method valueOfMethod = enumClass.getMethod("valueOf", String.class);
-            try {
-                final Object result = valueOfMethod.invoke(null, input);
-                return (E) result;
-            } catch (InvocationTargetException e) {
-                throw e.getCause();
-            }
+            return Enum.valueOf(enumClass, input);
         } catch (IllegalArgumentException e) {
             /* noop */
             //LOGGER.trace("input=" + input + " does not exist in enumClass=" + enumClass.getSimpleName());
@@ -267,4 +273,57 @@ public class JavaHelper {
         return false;
     }
 
+    public static String makeThreadName(final PwmApplication pwmApplication, final Class theClass) {
+        String instanceName = "-";
+        if (pwmApplication != null && pwmApplication.getInstanceID() != null) {
+            instanceName = pwmApplication.getInstanceID();
+        }
+
+        return PwmConstants.PWM_APP_NAME + "-" + instanceName + "-" + theClass.getSimpleName();
+    }
+
+    public static Properties newSortedProperties() {
+        return new Properties() {
+            public synchronized Enumeration<Object> keys() {
+                return Collections.enumeration(new TreeSet<>(super.keySet()));
+            }
+        };
+    }
+
+    public static ThreadFactory makePwmThreadFactory(final String namePrefix, final boolean daemon) {
+        return new ThreadFactory() {
+            private final ThreadFactory realThreadFactory = Executors.defaultThreadFactory();
+
+            @Override
+            public Thread newThread(final Runnable r) {
+                final Thread t = realThreadFactory.newThread(r);
+                t.setDaemon(daemon);
+                if (namePrefix != null) {
+                    final String newName = namePrefix + t.getName();
+                    t.setName(newName);
+                }
+                return t;
+            }
+        };
+    }
+
+    public static Collection<Method> getAllMethodsForClass(final Class clazz) {
+        final LinkedHashSet<Method> methods = new LinkedHashSet<>();
+
+        // add local methods;
+        methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
+
+        final Class superClass = clazz.getSuperclass();
+        if (superClass != null) {
+            methods.addAll(getAllMethodsForClass(superClass));
+        }
+
+        return Collections.unmodifiableSet(methods);
+    }
+
+    public static CSVPrinter makeCsvPrinter(final OutputStream outputStream)
+            throws IOException
+    {
+        return new CSVPrinter(new OutputStreamWriter(outputStream,PwmConstants.DEFAULT_CHARSET), PwmConstants.DEFAULT_CSV_FORMAT);
+    }
 }

+ 31 - 0
src/main/java/password/pwm/util/java/StringUtil.java

@@ -33,6 +33,7 @@ import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.text.NumberFormat;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -157,6 +158,36 @@ public abstract class StringUtil {
         return StringUtils.join(inputs == null ? new String[]{} : inputs.toArray(), separator);
     }
 
+    public static String formatDiskSize(final long diskSize) {
+        final float COUNT = 1000;
+        if (diskSize < 1) {
+            return "n/a";
+        }
+
+        if (diskSize == 0) {
+            return "0";
+        }
+
+        final NumberFormat nf = NumberFormat.getInstance();
+        nf.setMaximumFractionDigits(2);
+
+        if (diskSize > COUNT * COUNT * COUNT) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append(nf.format(diskSize / COUNT / COUNT / COUNT));
+            sb.append(" GB");
+            return sb.toString();
+        }
+
+        if (diskSize > COUNT * COUNT) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append(nf.format(diskSize / COUNT / COUNT));
+            sb.append(" MB");
+            return sb.toString();
+        }
+
+        return NumberFormat.getInstance().format(diskSize) + " bytes";
+    }
+
     public enum Base64Options {
         GZIP,
         URL_SAFE,

+ 5 - 5
src/main/java/password/pwm/util/localdb/Derby_LocalDB.java

@@ -25,7 +25,7 @@ package password.pwm.util.localdb;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.util.java.FileSystemUtility;
-import password.pwm.util.Helper;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
@@ -144,16 +144,16 @@ public class Derby_LocalDB extends AbstractJDBC_LocalDB {
     private void reclaimAllSpace(final Connection dbConnection) {
         final java.util.Date startTime = new java.util.Date();
         final long startSize = FileSystemUtility.getFileDirectorySize(dbDirectory);
-        LOGGER.debug("beginning reclaim space in all tables startSize=" + Helper.formatDiskSize(startSize));
+        LOGGER.debug("beginning reclaim space in all tables startSize=" + StringUtil.formatDiskSize(startSize));
         for (final LocalDB.DB db : LocalDB.DB.values()) {
             reclaimSpace(dbConnection,db);
         }
         final long completeSize = FileSystemUtility.getFileDirectorySize(dbDirectory);
         final long sizeDifference = startSize - completeSize;
         LOGGER.debug("completed reclaim space in all tables; duration=" + TimeDuration.fromCurrent(startTime).asCompactString()
-                + ", startSize=" + Helper.formatDiskSize(startSize)
-                + ", completeSize=" + Helper.formatDiskSize(completeSize)
-                + ", sizeDifference=" + Helper.formatDiskSize(sizeDifference)
+                + ", startSize=" + StringUtil.formatDiskSize(startSize)
+                + ", completeSize=" + StringUtil.formatDiskSize(completeSize)
+                + ", sizeDifference=" + StringUtil.formatDiskSize(sizeDifference)
         );
     }
 

+ 2 - 3
src/main/java/password/pwm/util/localdb/LocalDBFactory.java

@@ -28,7 +28,6 @@ import password.pwm.config.Configuration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.util.java.FileSystemUtility;
-import password.pwm.util.Helper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -100,11 +99,11 @@ public class LocalDBFactory {
 
         final StringBuilder debugText = new StringBuilder();
         debugText.append("LocalDB open in ").append(openTime.asCompactString());
-        debugText.append(", db size: ").append(Helper.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation())));
+        debugText.append(", db size: ").append(StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation())));
         debugText.append(" at ").append(dbDirectory.toString());
         final long freeSpace = FileSystemUtility.diskSpaceRemaining(localDB.getFileLocation());
         if (freeSpace >= 0) {
-            debugText.append(", ").append(Helper.formatDiskSize(freeSpace)).append(" free");
+            debugText.append(", ").append(StringUtil.formatDiskSize(freeSpace)).append(" free");
         }
         LOGGER.info(debugText);
 

+ 1 - 2
src/main/java/password/pwm/util/localdb/LocalDBUtility.java

@@ -31,7 +31,6 @@ import password.pwm.PwmConstants;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.svc.stats.EventRateMeter;
-import password.pwm.util.Helper;
 import password.pwm.util.ProgressInfo;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.TransactionSizeCalculator;
@@ -116,7 +115,7 @@ public class LocalDBUtility {
         },30 * 1000, 30 * 1000);
 
 
-        try (CSVPrinter csvPrinter = Helper.makeCsvPrinter(new GZIPOutputStream(outputStream, GZIP_BUFFER_SIZE))) {
+        try (CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter(new GZIPOutputStream(outputStream, GZIP_BUFFER_SIZE))) {
             csvPrinter.printComment(PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " LocalDB export on " + JavaHelper.toIsoDate(new Date()));
             for (final LocalDB.DB loopDB : LocalDB.DB.values()) {
                 if (loopDB.isBackup()) {

+ 1 - 2
src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java

@@ -27,7 +27,6 @@ import password.pwm.PwmApplication;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
@@ -103,7 +102,7 @@ public class WorkQueueProcessor<W extends Serializable> {
 
         this.workerThread = new WorkerThread();
         workerThread.setDaemon(true);
-        workerThread.setName(Helper.makeThreadName(pwmApplication, sourceClass) + "-worker-");
+        workerThread.setName(JavaHelper.makeThreadName(pwmApplication, sourceClass) + "-worker-");
         workerThread.start();
     }
 

+ 6 - 6
src/main/java/password/pwm/util/logging/LocalDBLogger.java

@@ -28,9 +28,9 @@ import password.pwm.error.PwmException;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
 import password.pwm.svc.PwmService;
-import password.pwm.util.Helper;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
@@ -113,14 +113,14 @@ public class LocalDBLogger implements PwmService {
         status = STATUS.OPEN;
 
         cleanerService = Executors.newSingleThreadScheduledExecutor(
-                Helper.makePwmThreadFactory(
-                        Helper.makeThreadName(pwmApplication, this.getClass()) + "-cleaner-",
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName(pwmApplication, this.getClass()) + "-cleaner-",
                         true
                 ));
 
         writerService = Executors.newSingleThreadScheduledExecutor(
-                Helper.makePwmThreadFactory(
-                        Helper.makeThreadName(pwmApplication, this.getClass()) + "-writer-",
+                JavaHelper.makePwmThreadFactory(
+                        JavaHelper.makeThreadName(pwmApplication, this.getClass()) + "-writer-",
                         true
                 ));
 
@@ -162,7 +162,7 @@ public class LocalDBLogger implements PwmService {
         sb.append(", tailAge=").append(tailAge == null ? "n/a" : TimeDuration.fromCurrent(tailAge).asCompactString());
         sb.append(", maxEvents=").append(settings.getMaxEvents());
         sb.append(", maxAge=").append(settings.getMaxAge().asCompactString());
-        sb.append(", localDBSize=").append(Helper.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation())));
+        sb.append(", localDBSize=").append(StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation())));
         return sb.toString();
     }
 

+ 1 - 2
src/main/java/password/pwm/ws/server/RestResultBean.java

@@ -27,7 +27,6 @@ import password.pwm.config.Configuration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.http.PwmRequest;
 import password.pwm.i18n.Message;
-import password.pwm.util.Helper;
 import password.pwm.util.java.JsonUtil;
 
 import javax.ws.rs.core.Response;
@@ -107,7 +106,7 @@ public class RestResultBean implements Serializable {
         final RestResultBean restResultBean = new RestResultBean();
         restResultBean.setError(true);
         restResultBean.setErrorMessage(errorInformation.toUserStr(locale, config));
-        if (forceDetail || Helper.determineIfDetailErrorMsgShown(pwmApplication)) {
+        if (forceDetail || pwmApplication.determineIfDetailErrorMsgShown()) {
             restResultBean.setErrorDetail(errorInformation.toDebugStr());
         }
         restResultBean.setErrorCode(errorInformation.getError().getErrorCode());

+ 2 - 3
src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp

@@ -30,7 +30,6 @@
 <%@ page import="password.pwm.svc.PwmService" %>
 <%@ page import="password.pwm.svc.sessiontrack.SessionTrackService" %>
 <%@ page import="password.pwm.svc.stats.Statistic" %>
-<%@ page import="password.pwm.util.Helper" %>
 <%@ page import="password.pwm.util.java.FileSystemUtility" %>
 <%@ page import="password.pwm.util.java.JavaHelper" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
@@ -482,7 +481,7 @@
                                         ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
                                         : dashboard_pwmApplication.getLocalDB().getFileLocation() == null
                                         ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                        : Helper.formatDiskSize(FileSystemUtility.getFileDirectorySize(
+                                        : StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(
                                         dashboard_pwmApplication.getLocalDB().getFileLocation()))
                                 %>
                             </td>
@@ -510,7 +509,7 @@
                                         ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
                                         : dashboard_pwmApplication.getLocalDB().getFileLocation() == null
                                         ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                                        : Helper.formatDiskSize(FileSystemUtility.diskSpaceRemaining(dashboard_pwmApplication.getLocalDB().getFileLocation())) %>
+                                        : StringUtil.formatDiskSize(FileSystemUtility.diskSpaceRemaining(dashboard_pwmApplication.getLocalDB().getFileLocation())) %>
                             </td>
                         </tr>
                         <tr>

+ 3 - 3
src/main/webapp/WEB-INF/jsp/configmanager-localdb.jsp

@@ -4,8 +4,8 @@
 <%@ page import="password.pwm.i18n.Config" %>
 <%@ page import="password.pwm.i18n.Display" %>
 <%@ page import="password.pwm.util.java.FileSystemUtility" %>
-<%@ page import="password.pwm.util.Helper" %>
 <%@ page import="password.pwm.util.LocaleHelper" %>
+<%@ page import="password.pwm.util.java.StringUtil" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -101,7 +101,7 @@
                             ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
                             : localdb_pwmApplication.getLocalDB().getFileLocation() == null
                             ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                            : Helper.formatDiskSize(FileSystemUtility.getFileDirectorySize(
+                            : StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(
                             localdb_pwmApplication.getLocalDB().getFileLocation()))
                     %>
                 </td>
@@ -115,7 +115,7 @@
                             ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
                             : localdb_pwmApplication.getLocalDB().getFileLocation() == null
                             ? JspUtility.getMessage(pageContext, Display.Value_NotApplicable)
-                            : Helper.formatDiskSize(FileSystemUtility.diskSpaceRemaining(localdb_pwmApplication.getLocalDB().getFileLocation())) %>
+                            : StringUtil.formatDiskSize(FileSystemUtility.diskSpaceRemaining(localdb_pwmApplication.getLocalDB().getFileLocation())) %>
                 </td>
             </tr>
         </table>

+ 1 - 0
src/test/java/password/pwm/error/PwmErrorTest.java

@@ -42,6 +42,7 @@ public class PwmErrorTest extends TestCase {
         }
     }
 
+    @Test
     public void testLocalizedMessage() {
         for (final PwmError pwmError : PwmError.values()) {
             pwmError.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE, null);

+ 160 - 0
src/test/java/password/pwm/http/servlet/ControlledPwmServletTest.java

@@ -0,0 +1,160 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.servlet;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.reflections.Reflections;
+import org.reflections.scanners.FieldAnnotationsScanner;
+import org.reflections.scanners.SubTypesScanner;
+import org.reflections.scanners.TypeAnnotationsScanner;
+import org.reflections.util.ClasspathHelper;
+import org.reflections.util.ConfigurationBuilder;
+import password.pwm.http.ProcessStatus;
+import password.pwm.http.PwmRequest;
+import password.pwm.util.java.JavaHelper;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ControlledPwmServletTest {
+    @Test
+    public void testProcess() throws IllegalAccessException, InstantiationException {
+        final Map<Class<? extends ControlledPwmServlet>,Map<String,Method>> dataMap = getClassAndMethods();
+
+        for (final Class<? extends ControlledPwmServlet> controlledPwmServlet : dataMap.keySet()) {
+            final Class<? extends AbstractPwmServlet.ProcessAction> processActionsClass = controlledPwmServlet.newInstance().getProcessActionsClass();
+            if (!processActionsClass.isEnum()) {
+                Assert.fail(controlledPwmServlet.getName() + " process action class must be an enum");
+            }
+        }
+    }
+
+    @Test
+    public void testActionHandlerReturnTypes() throws IllegalAccessException, InstantiationException {
+        final Map<Class<? extends ControlledPwmServlet>,Map<String,Method>> dataMap = getClassAndMethods();
+
+        for (final Class<? extends ControlledPwmServlet> controlledPwmServlet : dataMap.keySet()) {
+            final String servletName = controlledPwmServlet.getName();
+            for (final String methodName : dataMap.get(controlledPwmServlet).keySet()) {
+                final Method method = dataMap.get(controlledPwmServlet).get(methodName);
+                if (method.getReturnType() != ProcessStatus.class) {
+                    Assert.fail(servletName + ":" + method.getName() + " must have return type of " + ProcessStatus.class.getName());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testActionHandlerParameters() throws IllegalAccessException, InstantiationException {
+        final Map<Class<? extends ControlledPwmServlet>,Map<String,Method>> dataMap = getClassAndMethods();
+
+        for (final Class<? extends ControlledPwmServlet> controlledPwmServlet : dataMap.keySet()) {
+            final String servletName = controlledPwmServlet.getName();
+            for (final String methodName : dataMap.get(controlledPwmServlet).keySet()) {
+                final Method method = dataMap.get(controlledPwmServlet).get(methodName);
+                final Class[] returnTypes = method.getParameterTypes();
+                if (returnTypes.length != 1) {
+                    Assert.fail(servletName + ":" + method.getName() + " must have exactly one parameter");
+                }
+                if (!returnTypes[0].equals(PwmRequest.class)) {
+                    Assert.fail(servletName + ":" + method.getName() + " must have exactly one parameter of type " + PwmRequest.class.getName());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testActionHandlersExistence() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
+        final Map<Class<? extends ControlledPwmServlet>,Map<String,Method>> dataMap = getClassAndMethods();
+
+        for (final Class<? extends ControlledPwmServlet> controlledPwmServlet : dataMap.keySet()) {
+            final String servletName = controlledPwmServlet.getName();
+
+            final Class<? extends AbstractPwmServlet.ProcessAction> processActionsClass = controlledPwmServlet.newInstance().getProcessActionsClass();
+            final List<String> names = new ArrayList<>();
+            for (Object enumObject : processActionsClass.getEnumConstants()) {
+                names.add(((Enum)enumObject).name());
+            }
+
+            {
+                final Collection<String> missingActionHandlers = new HashSet<>(names);
+                missingActionHandlers.removeAll(dataMap.get(controlledPwmServlet).keySet());
+                if (!missingActionHandlers.isEmpty()) {
+                    Assert.fail(servletName + " does not have an action handler for action " + missingActionHandlers.iterator().next());
+                }
+            }
+
+            {
+                final Collection<String> superflousActionHandlers = new HashSet<>(dataMap.get(controlledPwmServlet).keySet());
+                superflousActionHandlers.removeAll(names);
+                if (!superflousActionHandlers.isEmpty()) {
+                    Assert.fail(servletName + " has an action handler for action " + superflousActionHandlers.iterator().next() + " but no such ProcessAction exists");
+                }
+            }
+        }
+    }
+
+    private Map<Class<? extends ControlledPwmServlet>,Map<String,Method>> getClassAndMethods() {
+        Reflections reflections = new Reflections(new ConfigurationBuilder()
+                .setUrls(ClasspathHelper.forPackage("password.pwm"))
+                .setScanners(new SubTypesScanner(),
+                        new TypeAnnotationsScanner(),
+                        new FieldAnnotationsScanner()
+                ));
+
+
+        new Reflections("password.pwm");
+
+        Set<Class<? extends ControlledPwmServlet>> classes = reflections.getSubTypesOf(ControlledPwmServlet.class);
+
+        final Map<Class<? extends ControlledPwmServlet>,Map<String,Method>> returnMap = new HashMap<>();
+
+        for (final Class<? extends ControlledPwmServlet> controlledPwmServlet : classes) {
+            if (!Modifier.isAbstract(controlledPwmServlet.getModifiers())) {
+
+                final Map<String, Method> annotatedMethods = new HashMap<>();
+
+                for (Method method : JavaHelper.getAllMethodsForClass(controlledPwmServlet)) {
+                    if (method.getAnnotation(ControlledPwmServlet.ActionHandler.class) != null) {
+                        final String actionName = method.getAnnotation(ControlledPwmServlet.ActionHandler.class).action();
+                        annotatedMethods.put(actionName, method);
+                    }
+                }
+
+                returnMap.put(controlledPwmServlet, Collections.unmodifiableMap(annotatedMethods));
+            }
+        }
+
+        return Collections.unmodifiableMap(returnMap);
+    }
+}

+ 4 - 4
src/test/java/password/pwm/manual/LocalDBLoggerTest.java

@@ -28,11 +28,11 @@ import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.svc.stats.EventRateMeter;
-import password.pwm.util.Helper;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.Percent;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBFactory;
@@ -176,9 +176,9 @@ public class LocalDBLoggerTest extends TestCase {
     private void outputDebugInfo() {
         final StringBuilder sb = new StringBuilder();
         sb.append("added ").append(numberFormat.format(eventsAdded.get()));
-        sb.append(", size: ").append(Helper.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation())));
+        sb.append(", size: ").append(StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation())));
         sb.append(", eventsInDb: ").append(figureEventsInDbStat());
-        sb.append(", free: ").append(Helper.formatDiskSize(
+        sb.append(", free: ").append(StringUtil.formatDiskSize(
                 FileSystemUtility.diskSpaceRemaining(localDB.getFileLocation())));
         sb.append(", eps: ").append(eventRateMeter.readEventRate().setScale(0, RoundingMode.UP));
         sb.append(", remain: ").append(settings.testDuration.subtract(TimeDuration.fromCurrent(startTime)).asCompactString());
@@ -199,7 +199,7 @@ public class LocalDBLoggerTest extends TestCase {
         results.dbClass = config.readAppProperty(AppProperty.LOCALDB_IMPLEMENTATION);
         results.duration = TimeDuration.fromCurrent(startTime).asCompactString();
         results.recordsAdded = eventsAdded.get();
-        results.dbSize = Helper.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation()));
+        results.dbSize = StringUtil.formatDiskSize(FileSystemUtility.getFileDirectorySize(localDB.getFileLocation()));
         results.eventsInDb = figureEventsInDbStat();
         return results;
     }