Browse Source

forgotten password refactoring to controlled servlet

Jason Rivard 8 years ago
parent
commit
cf7709628e

+ 40 - 7
src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java

@@ -29,6 +29,7 @@ import password.pwm.error.PwmUnrecoverableException;
 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.logging.PwmLogger;
 
 import javax.servlet.ServletException;
@@ -69,7 +70,7 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         throw new IllegalStateException("unable to determine PwmServletDefinition for class " + this.getClass().getName());
     }
 
-    ProcessStatus dispatchMethod(
+    private ProcessStatus dispatchMethod(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException
@@ -83,14 +84,28 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
             final Method interestedMethod = discoverMethodForAction(this.getClass(), action);
             if (interestedMethod != null) {
                 interestedMethod.setAccessible(true);
-                return (ProcessStatus)interestedMethod.invoke(this, pwmRequest);
+                return (ProcessStatus) interestedMethod.invoke(this, pwmRequest);
             }
-        } catch (IllegalAccessException | InvocationTargetException e) {
-            final String msg = "unable to discover/invoke action handler for '" + action + "', error: " + e.getMessage();
+        } catch (InvocationTargetException e) {
+            final Throwable cause = e.getCause();
+            if (cause != null) {
+                final String msg = "unexpected error during action handler for '" + action + "', error: " + e.getMessage();
+                LOGGER.error(msg, cause);
+                if (cause instanceof PwmUnrecoverableException) {
+                    throw (PwmUnrecoverableException) cause;
+                }
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, msg));
+            }
+            LOGGER.error("uncaused invocation error: " + e.getMessage(),e);
+        } catch (Throwable e) {
+            final String msg = "unexpected error invoking action handler for '" + action + "', error: " + e.getMessage();
             LOGGER.error(msg,e);
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, msg));
         }
-        throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, "missing handler for action " + action));
+
+        final String msg = "missing action handler for '" + action + "'";
+        LOGGER.error(msg);
+        throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, msg));
     }
 
     protected void processAction(final PwmRequest pwmRequest)
@@ -118,12 +133,14 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
             return;
         }
 
+        examineLastError(pwmRequest);
+
         nextStep(pwmRequest);
     }
 
-    abstract void nextStep(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException;
+    protected abstract void nextStep(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException;
 
-    abstract void preProcessCheck(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException;
+    public abstract void preProcessCheck(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException;
 
     void sendOtherRedirect(final PwmRequest pwmRequest, final String location) throws IOException, PwmUnrecoverableException {
         final String protocol = pwmRequest.getHttpServletRequest().getProtocol();
@@ -167,5 +184,21 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
 
         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);
+    }
+
+    private void examineLastError(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+        final Class<? extends PwmSessionBean> beanClass = this.getServletDefinition().getPwmSessionBeanClass();
+        final PwmSessionBean pwmSessionBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, beanClass);
+        if (pwmSessionBean != null && pwmSessionBean.getLastError() != null) {
+            pwmRequest.setResponseError(pwmSessionBean.getLastError());
+            pwmSessionBean.setLastError(null);
+        }
+    }
 }
 

+ 40 - 30
src/main/java/password/pwm/http/servlet/PwmServletDefinition.java

@@ -25,6 +25,10 @@ package password.pwm.http.servlet;
 import password.pwm.error.ErrorInformation;
 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.PwmSessionBean;
+import password.pwm.http.bean.UpdateProfileBean;
 import password.pwm.http.servlet.admin.AdminServlet;
 import password.pwm.http.servlet.changepw.PrivateChangePasswordServlet;
 import password.pwm.http.servlet.changepw.PublicChangePasswordServlet;
@@ -43,46 +47,48 @@ import javax.servlet.annotation.WebServlet;
 import java.lang.annotation.Annotation;
 
 public enum PwmServletDefinition {
-    Login(password.pwm.http.servlet.LoginServlet.class),
-    Logout(password.pwm.http.servlet.LogoutServlet.class),
-    OAuthConsumer(OAuthConsumerServlet.class),
-    Command(password.pwm.http.servlet.CommandServlet.class),
-    PublicPeopleSearch(PublicPeopleSearchServlet.class),
-    PublicChangePassword(PublicChangePasswordServlet.class),
+    Login(password.pwm.http.servlet.LoginServlet.class, LoginServletBean.class),
+    Logout(password.pwm.http.servlet.LogoutServlet.class, null),
+    OAuthConsumer(OAuthConsumerServlet.class, null),
+    Command(password.pwm.http.servlet.CommandServlet.class, null),
+    PublicPeopleSearch(PublicPeopleSearchServlet.class, null),
+    PublicChangePassword(PublicChangePasswordServlet.class, null),
     //Resource(password.pwm.http.servlet.ResourceFileServlet.class),
 
-    AccountInformation(AccountInformationServlet.class),
-    PrivateChangePassword(PrivateChangePasswordServlet.class),
-    SetupResponses(password.pwm.http.servlet.SetupResponsesServlet.class),
-    UpdateProfile(password.pwm.http.servlet.UpdateProfileServlet.class),
-    SetupOtp(password.pwm.http.servlet.SetupOtpServlet.class),
-    Helpdesk(password.pwm.http.servlet.helpdesk.HelpdeskServlet.class),
-    Shortcuts(password.pwm.http.servlet.ShortcutServlet.class),
-    PrivatePeopleSearch(PrivatePeopleSearchServlet.class),
-    GuestRegistration(password.pwm.http.servlet.GuestRegistrationServlet.class),
-    SelfDelete(DeleteAccountServlet.class),
-
-    Admin(AdminServlet.class),
-    ConfigGuide(ConfigGuideServlet.class),
-    ConfigEditor(ConfigEditorServlet.class),
-    ConfigManager(ConfigManagerServlet.class),
-    ConfigManager_Wordlists(ConfigManagerWordlistServlet.class),
-    ConfigManager_LocalDB(ConfigManagerLocalDBServlet.class),
-    ConfigManager_Certificates(ConfigManagerCertificatesServlet.class),
-
-    NewUser(NewUserServlet.class),
-    ActivateUser(password.pwm.http.servlet.ActivateUserServlet.class),
-    ForgottenPassword(password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet.class),
-    ForgottenUsername(password.pwm.http.servlet.ForgottenUsernameServlet.class),
+    AccountInformation(AccountInformationServlet.class, null),
+    PrivateChangePassword(PrivateChangePasswordServlet.class, null),
+    SetupResponses(password.pwm.http.servlet.SetupResponsesServlet.class, null),
+    UpdateProfile(password.pwm.http.servlet.UpdateProfileServlet.class, UpdateProfileBean.class),
+    SetupOtp(password.pwm.http.servlet.SetupOtpServlet.class, null),
+    Helpdesk(password.pwm.http.servlet.helpdesk.HelpdeskServlet.class, null),
+    Shortcuts(password.pwm.http.servlet.ShortcutServlet.class, null),
+    PrivatePeopleSearch(PrivatePeopleSearchServlet.class, null),
+    GuestRegistration(password.pwm.http.servlet.GuestRegistrationServlet.class, null),
+    SelfDelete(DeleteAccountServlet.class, null),
+
+    Admin(AdminServlet.class, null),
+    ConfigGuide(ConfigGuideServlet.class, null),
+    ConfigEditor(ConfigEditorServlet.class, null),
+    ConfigManager(ConfigManagerServlet.class, null),
+    ConfigManager_Wordlists(ConfigManagerWordlistServlet.class, null),
+    ConfigManager_LocalDB(ConfigManagerLocalDBServlet.class, null),
+    ConfigManager_Certificates(ConfigManagerCertificatesServlet.class, null),
+
+    NewUser(NewUserServlet.class, null),
+    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),
 
     ;
 
     private final String[] patterns;
     private final String servletUrl;
     private final Class<? extends PwmServlet> pwmServletClass;
+    private final Class<? extends PwmSessionBean> pwmSessionBeanClass;
 
-    PwmServletDefinition(final Class<? extends PwmServlet> pwmServletClass) {
+    PwmServletDefinition(final Class<? extends PwmServlet> pwmServletClass, final Class<? extends PwmSessionBean> pwmSessionBeanClass) {
         this.pwmServletClass = pwmServletClass;
+        this.pwmSessionBeanClass = pwmSessionBeanClass;
 
         try {
             this.patterns = getWebServletAnnotation(pwmServletClass).urlPatterns();
@@ -111,6 +117,10 @@ public enum PwmServletDefinition {
         return pwmServletClass;
     }
 
+    public Class<? extends PwmSessionBean> getPwmSessionBeanClass() {
+        return pwmSessionBeanClass;
+    }
+
     private WebServlet getWebServletAnnotation(final Class<? extends PwmServlet> pwmServletClass) throws PwmUnrecoverableException {
         for (final Annotation annotation : pwmServletClass.getDeclaredAnnotations()) {
             if (annotation instanceof WebServlet) {

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

@@ -241,7 +241,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
                 errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT);
             }
             LOGGER.debug(pwmSession, errorInformation.toDebugStr());
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
         }
 
         return ProcessStatus.Continue;
@@ -341,7 +341,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
             readFormParametersFromRequest(pwmRequest, updateAttributesProfile, updateProfileBean);
         } catch (PwmOperationalException e) {
             LOGGER.error(pwmRequest, e.getMessage());
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
         }
 
         updateProfileBean.setFormSubmitted(true);
@@ -349,7 +349,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
         return ProcessStatus.Continue;
     }
 
-    void nextStep(
+    protected void nextStep(
             final PwmRequest pwmRequest)
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
@@ -396,7 +396,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
             verifyFormAttributes(pwmRequest, formValues, true);
         } catch (PwmException e) {
             LOGGER.error(pwmSession, e.getMessage());
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             forwardToForm(pwmRequest, updateAttributesProfile, updateProfileBean);
             return;
         }
@@ -432,18 +432,18 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
             return;
         } catch (PwmException e) {
             LOGGER.error(pwmSession, e.getMessage());
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
         } catch (ChaiException e) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UPDATE_ATTRS_FAILURE,e.toString());
             LOGGER.error(pwmSession, errorInformation.toDebugStr());
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
         }
 
         forwardToForm(pwmRequest, updateAttributesProfile, updateProfileBean);
     }
 
     @Override
-    void preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+    public void preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         if (!pwmRequest.getPwmApplication().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;

+ 0 - 11
src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -392,11 +392,6 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final Configuration config = pwmApplication.getConfig();
 
-        if (changePasswordBean.getLastError() != null) {
-            pwmRequest.setResponseError(changePasswordBean.getLastError());
-            changePasswordBean.setLastError(null);
-        }
-
         if (changePasswordBean.getChangeProgressTracker() != null) {
             forwardToWaitPage(pwmRequest);
             return;
@@ -634,10 +629,4 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
             throw PwmUnrecoverableException.fromChaiException(e);
         }
     }
-
-    private void setLastError(final PwmRequest pwmRequest, final ErrorInformation errorInformation) throws PwmUnrecoverableException {
-        final ChangePasswordBean changePasswordBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, ChangePasswordBean.class);
-        changePasswordBean.setLastError(errorInformation);
-        pwmRequest.setResponseError(errorInformation);
-    }
 }

+ 149 - 58
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -58,12 +58,14 @@ 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.bean.ForgottenPasswordBean;
 import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.oauth.OAuthForgottenPasswordResults;
 import password.pwm.http.servlet.oauth.OAuthMachine;
@@ -133,7 +135,7 @@ import java.util.Set;
                 PwmConstants.URL_PREFIX_PUBLIC + "/ForgottenPassword/*",
         }
 )
-public class ForgottenPasswordServlet extends AbstractPwmServlet {
+public class ForgottenPasswordServlet extends ControlledPwmServlet {
 // ------------------------------ FIELDS ------------------------------
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(ForgottenPasswordServlet.class);
@@ -167,6 +169,16 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         }
     }
 
+    public enum ActionChoice {
+        unlock,
+        resetPassword,
+    }
+
+    public enum TokenChoice {
+        email,
+        sms,
+    }
+
     protected ForgottenPasswordAction readProcessAction(final PwmRequest request)
             throws PwmUnrecoverableException
     {
@@ -177,7 +189,33 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         }
     }
 
+    @Override
+    public void preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+
+        final Configuration config = pwmApplication.getConfig();
+        final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
+
+        if (!config.readSettingAsBoolean(PwmSetting.FORGOTTEN_PASSWORD_ENABLE)) {
+            pwmRequest.respondWithError(PwmError.ERROR_SERVICE_NOT_AVAILABLE.toInfo());
+            return;
+        }
 
+        if (pwmSession.isAuthenticated()) {
+            pwmRequest.respondWithError(PwmError.ERROR_USERAUTHENTICATED.toInfo());
+            return;
+        }
+
+        if (forgottenPasswordBean.getUserIdentity() != null) {
+            pwmApplication.getIntruderManager().convenience().checkUserIdentity(forgottenPasswordBean.getUserIdentity());
+        }
+
+        checkForLocaleSwitch(pwmRequest, forgottenPasswordBean);
+    }
+
+    /*
     @Override
     public void processAction(final PwmRequest pwmRequest)
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
@@ -272,36 +310,50 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         }
 
         if (!pwmRequest.getPwmResponse().isCommitted()) {
-            this.advancedToNextStage(pwmRequest);
+            this.nextStep(pwmRequest);
         }
     }
+    */
 
-    private ForgottenPasswordBean forgottenPasswordBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+    private static ForgottenPasswordBean forgottenPasswordBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
         return pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, ForgottenPasswordBean.class);
     }
 
-    private void clearForgottenPasswordBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+    private static void clearForgottenPasswordBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
         pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, ForgottenPasswordBean.class);
     }
 
-    private void processActionChoice(final PwmRequest pwmRequest)
+    @ActionHandler(action = "actionChoice")
+    private ProcessStatus processActionChoice(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
 
         if (forgottenPasswordBean.getProgress().isAllPassed()) {
             final String choice = pwmRequest.readParameterAsString("choice");
-            if (choice != null) {
-                if ("unlock".equals(choice)) {
-                    this.executeUnlock(pwmRequest);
-                } else if ("resetPassword".equalsIgnoreCase(choice)) {
-                    this.executeResetPassword(pwmRequest);
+
+            final ActionChoice actionChoice = JavaHelper.readEnumFromString(ActionChoice.class, null, choice);
+            if (actionChoice != null) {
+                switch (actionChoice) {
+                    case unlock:
+                        this.executeUnlock(pwmRequest);
+                        break;
+
+                    case resetPassword:
+                        this.executeResetPassword(pwmRequest);
+                        break;
+
+                    default:
+                        JavaHelper.unhandledSwitchStatement(actionChoice);
                 }
             }
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processReset(final PwmRequest pwmRequest)
+    @ActionHandler(action = "reset")
+    private ProcessStatus processReset(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
@@ -311,25 +363,39 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         if (forgottenPasswordBean.getUserIdentity() == null) {
             pwmRequest.sendRedirectToContinue();
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processTokenChoice(final PwmRequest pwmRequest)
+    @ActionHandler(action = "tokenChoice")
+    private ProcessStatus processTokenChoice(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
         if (forgottenPasswordBean.getProgress().getTokenSendChoice() == MessageSendMethod.CHOICE_SMS_EMAIL) {
             final String choice = pwmRequest.readParameterAsString("choice");
-            if (choice != null) {
-                if ("email".equals(choice)) {
-                    forgottenPasswordBean.getProgress().setTokenSendChoice(MessageSendMethod.EMAILONLY);
-                } else if ("sms".equalsIgnoreCase(choice)) {
-                    forgottenPasswordBean.getProgress().setTokenSendChoice(MessageSendMethod.SMSONLY);
+            final TokenChoice tokenChoice = JavaHelper.readEnumFromString(TokenChoice.class, null, choice);
+            if (tokenChoice != null) {
+                switch (tokenChoice) {
+                    case email:
+                        forgottenPasswordBean.getProgress().setTokenSendChoice(MessageSendMethod.EMAILONLY);
+                        break;
+
+                    case sms:
+                        forgottenPasswordBean.getProgress().setTokenSendChoice(MessageSendMethod.SMSONLY);
+                        break;
+
+                    default:
+                        JavaHelper.unhandledSwitchStatement(tokenChoice);
                 }
             }
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processVerificationChoice(final PwmRequest pwmRequest)
+    @ActionHandler(action = "verificationChoice")
+    private ProcessStatus processVerificationChoice(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, ServletException, IOException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
@@ -346,9 +412,9 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             } catch (IllegalArgumentException e) {
                 final String errorMsg = "unknown verification method requested";
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,errorMsg);
-                pwmRequest.setResponseError(errorInformation);
+                setLastError(pwmRequest, errorInformation);
                 pwmRequest.forwardToJsp(JspUrl.RECOVER_PASSWORD_METHOD_CHOICE);
-                return;
+                return ProcessStatus.Halt;
             }
         }
 
@@ -356,17 +422,20 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             forgottenPasswordBean.getProgress().setInProgressVerificationMethod(requestedChoice);
             pwmRequest.setAttribute(PwmRequest.Attribute.ForgottenPasswordOptionalPageView,"true");
             forwardUserBasedOnRecoveryMethod(pwmRequest, requestedChoice);
-            return;
+            return ProcessStatus.Continue;
         } else if (requestedChoice != null) {
             final String errorMsg = "requested verification method is not available at this time";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,errorMsg);
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
         }
 
         pwmRequest.forwardToJsp(JspUrl.RECOVER_PASSWORD_METHOD_CHOICE);
+
+        return ProcessStatus.Halt;
     }
 
-    private void processSearch(final PwmRequest pwmRequest)
+    @ActionHandler(action = "search")
+    private ProcessStatus processSearch(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
@@ -382,16 +451,15 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
             final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
             LOGGER.debug(pwmRequest, errorInfo);
-            pwmRequest.setResponseError(errorInfo);
-            forwardToSearchPage(pwmRequest);
-            return;
+            setLastError(pwmRequest, errorInfo);
+            return ProcessStatus.Continue;
         }
 
 
         final List<FormConfiguration> forgottenPasswordForm = pwmApplication.getConfig().readSettingAsForm(
                 PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM);
 
-        Map<FormConfiguration, String> formValues = new HashMap<>();
+        Map<FormConfiguration, String> formValues = new LinkedHashMap<>();
 
         try {
             //read the values from the request
@@ -429,9 +497,8 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             if (userIdentity == null) {
                 pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
                 pwmApplication.getStatisticsManager().incrementValue(Statistic.RECOVERY_FAILURES);
-                pwmRequest.setResponseError(PwmError.ERROR_CANT_MATCH_USER.toInfo());
-                forwardToSearchPage(pwmRequest);
-                return;
+                setLastError(pwmRequest, PwmError.ERROR_CANT_MATCH_USER.toInfo());
+                return ProcessStatus.Continue;
             }
 
             AuthenticationUtility.checkIfUserEligibleToAuthentication(pwmApplication, userIdentity);
@@ -447,12 +514,14 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             pwmApplication.getIntruderManager().convenience().markAttributes(formValues, pwmSession);
 
             LOGGER.debug(pwmSession,errorInfo.toDebugStr());
-            pwmRequest.setResponseError(errorInfo);
-            forwardToSearchPage(pwmRequest);
+            setLastError(pwmRequest, errorInfo);
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processEnterToken(final PwmRequest pwmRequest)
+    @ActionHandler(action = "enterCode")
+    private ProcessStatus processEnterToken(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
@@ -490,9 +559,12 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             }
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processEnterNaaf(final PwmRequest pwmRequest)
+    @ActionHandler(action = "enterNaafResponse")
+    private ProcessStatus processEnterNaaf(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ServletException
     {
         final String PREFIX = "naaf-";
@@ -522,16 +594,19 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             pwmRequest.respondWithError(errorInformation,true);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
             LOGGER.debug(pwmRequest, "unsuccessful NAAF verification input: " + errorInformation.toDebugStr());
-            return;
+            return ProcessStatus.Continue;
         }
 
         if (errorInformation != null) {
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processEnterRemote(final PwmRequest pwmRequest)
+    @ActionHandler(action = "enterRemoteResponse")
+    private ProcessStatus processEnterRemote(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ServletException
     {
         final String PREFIX = "remote-";
@@ -561,16 +636,19 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             pwmRequest.respondWithError(errorInformation,true);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
             LOGGER.debug(pwmRequest, "unsuccessful remote response verification input: " + errorInformation.toDebugStr());
-            return;
+            return ProcessStatus.Continue;
         }
 
         if (errorInformation != null) {
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processEnterOtpToken(final PwmRequest pwmRequest)
+    @ActionHandler(action = "enterOtp")
+    private ProcessStatus processEnterOtpToken(final PwmRequest pwmRequest)
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
@@ -605,9 +683,12 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                 handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_OTP_TOKEN,e.getErrorInformation().toDebugStr()));
             }
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processOAuthReturn(final PwmRequest pwmRequest)
+    @ActionHandler(action = "oauthReturn")
+    private ProcessStatus processOAuthReturn(final PwmRequest pwmRequest)
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
@@ -615,14 +696,14 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             LOGGER.debug(pwmRequest, "oauth return detected, however current session did not issue an oauth request; will restart forgotten password sequence");
             pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, ForgottenPasswordBean.class);
             pwmRequest.sendRedirect(PwmServletDefinition.ForgottenPassword);
-            return;
+            return ProcessStatus.Halt;
         }
 
         if (forgottenPasswordBean.getUserIdentity() == null) {
             LOGGER.debug(pwmRequest, "oauth return detected, however current session does not have a user identity stored; will restart forgotten password sequence");
             pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, ForgottenPasswordBean.class);
             pwmRequest.sendRedirect(PwmServletDefinition.ForgottenPassword);
-            return;
+            return ProcessStatus.Halt;
         }
 
         final String encryptedResult = pwmRequest.readParameterAsString(PwmConstants.PARAM_RECOVERY_OAUTH_RESULT, PwmHttpRequestWrapper.Flag.BypassValidation);
@@ -661,16 +742,18 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR, errorMsg);
             throw new PwmUnrecoverableException(errorInformation);
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void processCheckResponses(final PwmRequest pwmRequest)
+    @ActionHandler(action = "checkResponses")
+    private ProcessStatus processCheckResponses(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
     {
-        //final SessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
 
         if (forgottenPasswordBean.getUserIdentity() == null) {
-            return;
+            return ProcessStatus.Continue;
         }
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
 
@@ -706,26 +789,29 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                 final String errorMsg = "incorrect response to one or more challenges";
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE, errorMsg);
                 handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
-                return;
+                return ProcessStatus.Continue;
             }
         } catch (ChaiValidationException e) {
             LOGGER.debug(pwmRequest, "chai validation error checking user responses: " + e.getMessage());
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.forChaiError(e.getErrorCode()));
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
-            return;
+            return ProcessStatus.Continue;
         }
 
         forgottenPasswordBean.getProgress().getSatisfiedMethods().add(IdentityVerificationMethod.CHALLENGE_RESPONSES);
+
+        return ProcessStatus.Continue;
     }
 
-    private void processCheckAttributes(final PwmRequest pwmRequest)
+    @ActionHandler(action = "checkAttributes")
+    private ProcessStatus processCheckAttributes(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
     {
         //final SessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
 
         if (forgottenPasswordBean.getUserIdentity() == null) {
-            return;
+            return ProcessStatus.Continue;
         }
         final UserIdentity userIdentity =forgottenPasswordBean.getUserIdentity();
 
@@ -736,7 +822,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             final List<FormConfiguration> requiredAttributesForm = forgottenPasswordBean.getAttributeForm();
 
             if (requiredAttributesForm.isEmpty()) {
-                return;
+                return ProcessStatus.Continue;
             }
 
             final Map<FormConfiguration,String> formValues = FormUtility.readFormValuesFromRequest(
@@ -760,9 +846,12 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         } catch (PwmDataValidationException e) {
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE,e.getErrorInformation().toDebugStr()));
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void advancedToNextStage(final PwmRequest pwmRequest)
+    @Override
+    protected void nextStep(final PwmRequest pwmRequest)
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -992,7 +1081,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         }
     }
 
-    private void processSendNewPassword(final PwmRequest pwmRequest)
+    private static void processSendNewPassword(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -1117,7 +1206,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                     userIdentity
             );
             if (userLastPasswordChange != null) {
-                final String userChangeString = PwmConstants.DEFAULT_DATETIME_FORMAT.format(userLastPasswordChange);
+                final String userChangeString = JavaHelper.toIsoDate(userLastPasswordChange);
                 tokenMapData.put(PwmConstants.TOKEN_KEY_PWD_CHG_DATE, userChangeString);
             }
         } catch (ChaiUnavailableException e) {
@@ -1215,7 +1304,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         return returnList;
     }
 
-    private void addPostChangeAction(
+    private static void addPostChangeAction(
             final PwmRequest pwmRequest,
             final UserIdentity userIdentity
     )
@@ -1448,7 +1537,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             throws PwmUnrecoverableException
     {
         LOGGER.debug(pwmRequest, errorInformation);
-        pwmRequest.setResponseError(errorInformation);
+        setLastError(pwmRequest, errorInformation);
 
         final UserIdentity userIdentity = forgottenPasswordBean == null
                 ? null
@@ -1492,7 +1581,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             clearForgottenPasswordBean(pwmRequest);
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, "unexpected error while re-loading user data due to locale change: " + e.getErrorInformation().toDebugStr());
             LOGGER.error(pwmRequest, errorInformation.toDebugStr());
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
         }
     }
 
@@ -1563,7 +1652,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         return Collections.unmodifiableSet(result);
     }
 
-    public RecoveryAction getRecoveryAction(final Configuration configuration, final ForgottenPasswordBean forgottenPasswordBean) {
+    public static RecoveryAction getRecoveryAction(final Configuration configuration, final ForgottenPasswordBean forgottenPasswordBean) {
         final ForgottenPasswordProfile forgottenPasswordProfile = configuration.getForgottenPasswordProfiles().get(forgottenPasswordBean.getForgottenPasswordProfileID());
         return forgottenPasswordProfile.readSettingAsEnum(PwmSetting.RECOVERY_ACTION, RecoveryAction.class);
     }
@@ -1823,6 +1912,8 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                 macroMachine
         );
     }
+
+
 }
 
 

+ 4 - 0
src/main/java/password/pwm/http/state/SessionStateService.java

@@ -121,6 +121,10 @@ public class SessionStateService implements PwmService {
     }
 
     public <E extends PwmSessionBean> E getBean(final PwmRequest pwmRequest, final Class<E> theClass) throws PwmUnrecoverableException {
+        if (theClass == null) {
+            return null;
+        }
+
         if (beanSupportsMode(theClass, SessionBeanMode.CRYPTCOOKIE)) {
             return sessionBeanProvider.getSessionBean(pwmRequest, theClass);
         }

+ 2 - 2
src/main/java/password/pwm/util/java/JavaHelper.java

@@ -129,13 +129,13 @@ public class JavaHelper {
         return new String(chars);
     }
 
-    public static Date nextZuluZeroTime() {
+    public static Instant nextZuluZeroTime() {
         final Calendar nextZuluMidnight = GregorianCalendar.getInstance(TimeZone.getTimeZone("Zulu"));
         nextZuluMidnight.set(Calendar.HOUR_OF_DAY,0);
         nextZuluMidnight.set(Calendar.MINUTE,0);
         nextZuluMidnight.set(Calendar.SECOND, 0);
         nextZuluMidnight.add(Calendar.HOUR, 24);
-        return nextZuluMidnight.getTime();
+        return nextZuluMidnight.getTime().toInstant();
     }
 
     public static <E extends Enum<E>> List<E> readEnumListFromStringCollection(final Class<E> enumClass, final Collection<String> inputs ) {