Jason Rivard пре 8 година
родитељ
комит
e424988169
58 измењених фајлова са 651 додато и 438 уклоњено
  1. 2 0
      src/main/java/password/pwm/AppProperty.java
  2. 6 8
      src/main/java/password/pwm/config/Configuration.java
  3. 2 0
      src/main/java/password/pwm/config/PwmSetting.java
  4. 2 1
      src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java
  5. 3 8
      src/main/java/password/pwm/http/PwmRequest.java
  6. 2 2
      src/main/java/password/pwm/http/PwmResponse.java
  7. 17 2
      src/main/java/password/pwm/http/bean/ChangePasswordBean.java
  8. 8 2
      src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java
  9. 1 1
      src/main/java/password/pwm/http/filter/ConfigAccessFilter.java
  10. 19 0
      src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java
  11. 4 4
      src/main/java/password/pwm/http/servlet/ActivateUserServlet.java
  12. 134 117
      src/main/java/password/pwm/http/servlet/CommandServlet.java
  13. 0 19
      src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java
  14. 43 53
      src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java
  15. 3 3
      src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  16. 6 6
      src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  17. 1 1
      src/main/java/password/pwm/http/servlet/LoginServlet.java
  18. 4 4
      src/main/java/password/pwm/http/servlet/SetupOtpServlet.java
  19. 22 6
      src/main/java/password/pwm/http/servlet/UpdateProfileServlet.java
  20. 3 3
      src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  21. 56 7
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  22. 2 2
      src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  23. 1 2
      src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java
  24. 43 83
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  25. 18 0
      src/main/java/password/pwm/http/servlet/peoplesearch/PrivatePeopleSearchServlet.java
  26. 20 0
      src/main/java/password/pwm/http/servlet/peoplesearch/PublicPeopleSearchServlet.java
  27. 15 13
      src/main/java/password/pwm/http/tag/ErrorMessageTag.java
  28. 4 0
      src/main/java/password/pwm/http/tag/PwmFormIDTag.java
  29. 1 0
      src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  30. 1 0
      src/main/java/password/pwm/i18n/Message.java
  31. 1 1
      src/main/java/password/pwm/svc/wordlist/Populator.java
  32. 0 38
      src/main/java/password/pwm/util/macro/InternalMacros.java
  33. 37 1
      src/main/java/password/pwm/util/macro/StandardMacros.java
  34. 2 0
      src/main/resources/password/pwm/AppProperty.properties
  35. 9 1
      src/main/resources/password/pwm/config/PwmSetting.xml
  36. 2 2
      src/main/resources/password/pwm/i18n/ConfigGuide.properties
  37. 3 1
      src/main/resources/password/pwm/i18n/Display.properties
  38. 1 0
      src/main/resources/password/pwm/i18n/Message.properties
  39. 3 1
      src/main/resources/password/pwm/i18n/PwmSetting.properties
  40. 3 3
      src/main/webapp/WEB-INF/jsp/accountinformation.jsp
  41. 2 3
      src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp
  42. 6 6
      src/main/webapp/WEB-INF/jsp/changepassword-complete.jsp
  43. 2 4
      src/main/webapp/WEB-INF/jsp/error-http.jsp
  44. 2 2
      src/main/webapp/WEB-INF/jsp/error.jsp
  45. 41 2
      src/main/webapp/WEB-INF/jsp/forgottenpassword-entertoken.jsp
  46. 2 3
      src/main/webapp/WEB-INF/jsp/forgottenpassword-method.jsp
  47. 2 2
      src/main/webapp/WEB-INF/jsp/forgottenpassword-tokenchoice.jsp
  48. 2 2
      src/main/webapp/WEB-INF/jsp/forgottenusername-complete.jsp
  49. 7 6
      src/main/webapp/WEB-INF/jsp/fragment/cancel-form.jsp
  50. 1 1
      src/main/webapp/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp
  51. 56 0
      src/main/webapp/WEB-INF/jsp/fragment/token-form-field.jsp
  52. 1 2
      src/main/webapp/WEB-INF/jsp/newuser-entercode.jsp
  53. 2 1
      src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp
  54. 2 1
      src/main/webapp/WEB-INF/jsp/success.jsp
  55. 1 2
      src/main/webapp/WEB-INF/jsp/updateprofile-entercode.jsp
  56. 5 5
      src/main/webapp/public/index.jsp
  57. 1 1
      src/main/webapp/public/resources/js/main.js
  58. 12 0
      src/main/webapp/public/resources/style.css

+ 2 - 0
src/main/java/password/pwm/AppProperty.java

@@ -256,6 +256,8 @@ public enum     AppProperty {
     TOKEN_REMOVAL_DELAY_MS                          ("token.removalDelayMS"),
     TOKEN_PURGE_BATCH_SIZE                          ("token.purgeBatchSize"),
     TOKEN_MAX_UNIQUE_CREATE_ATTEMPTS                ("token.maxUniqueCreateAttempts"),
+    TOKEN_RESEND_ENABLED                            ("token.resend.enabled"),
+    TOKEN_RESEND_DELAY_MS                           ("token.resend.delayMS"),
 
     /** Regular expression to be used for matching URLs to be shortened by the URL Shortening Service Class. */
     URL_SHORTNER_URL_REGEX                          ("urlshortener.url.regex"),

+ 6 - 8
src/main/java/password/pwm/config/Configuration.java

@@ -75,8 +75,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -411,7 +409,7 @@ public class Configuration implements Serializable, SettingReader {
 
         final PwmPasswordPolicy policy = initPasswordPolicy(profile,locale);
         if (!dataCache.cachedPasswordPolicy.containsKey(profile)) {
-            dataCache.cachedPasswordPolicy.put(profile,new HashMap<Locale,PwmPasswordPolicy>());
+            dataCache.cachedPasswordPolicy.put(profile,new LinkedHashMap<Locale,PwmPasswordPolicy>());
         }
         dataCache.cachedPasswordPolicy.get(profile).put(locale,policy);
         return policy;
@@ -776,12 +774,12 @@ public class Configuration implements Serializable, SettingReader {
     }
 
     private static class DataCache implements Serializable {
-        private final Map<String,Map<Locale,PwmPasswordPolicy>> cachedPasswordPolicy = new HashMap<>();
+        private final Map<String,Map<Locale,PwmPasswordPolicy>> cachedPasswordPolicy = new LinkedHashMap<>();
         private Map<Locale,String> localeFlagMap = null;
         private Map<String,LdapProfile> ldapProfiles;
         private final Map<PwmSetting, StoredValue> settings = new EnumMap<>(PwmSetting.class);
-        private final Map<String,Map<Locale,String>> customText = new HashMap<>();
-        private final Map<ProfileType,Map<String,Profile>> profileCache = new HashMap<>();
+        private final Map<String,Map<Locale,String>> customText = new LinkedHashMap<>();
+        private final Map<ProfileType,Map<String,Profile>> profileCache = new LinkedHashMap<>();
     }
 
     public Map<AppProperty,String> readAllNonDefaultAppProperties() {
@@ -837,7 +835,7 @@ public class Configuration implements Serializable, SettingReader {
 
     public Map<String,Profile> profileMap(final ProfileType profileType) {
         if (!dataCache.profileCache.containsKey(profileType)) {
-            dataCache.profileCache.put(profileType,new LinkedHashMap<String, Profile>());
+            dataCache.profileCache.put(profileType,new LinkedHashMap<>());
             for (final String profileID : ProfileUtility.profileIDsForCategory(this, profileType.getCategory())) {
                 final Profile newProfile = newProfileForID(profileType, profileID);
                 dataCache.profileCache.get(profileType).put(profileID, newProfile);
@@ -892,7 +890,7 @@ public class Configuration implements Serializable, SettingReader {
     }
 
     public Set<PwmSetting> nonDefaultSettings() {
-        final HashSet returnSet = new HashSet();
+        final Set returnSet = new LinkedHashSet();
         for (final StoredConfigurationImpl.SettingValueRecord valueRecord : this.storedConfiguration.modifiedSettings()) {
             returnSet.add(valueRecord.getSetting());
         }

+ 2 - 0
src/main/java/password/pwm/config/PwmSetting.java

@@ -111,6 +111,8 @@ public enum PwmSetting {
             "display.maskPasswordFields", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UI_FEATURES),
     DISPLAY_MASK_RESPONSE_FIELDS(
             "display.maskResponseFields", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UI_FEATURES),
+    DISPLAY_MASK_TOKEN_FIELDS(
+            "display.maskTokenFields", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UI_FEATURES),
     DISPLAY_CANCEL_BUTTON(
             "display.showCancelButton", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UI_FEATURES),
     DISPLAY_SUCCESS_PAGES(

+ 2 - 1
src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java

@@ -51,7 +51,8 @@ public class ForgottenPasswordProfile extends AbstractProfile {
 
     public static ForgottenPasswordProfile makeFromStoredConfiguration(final StoredConfiguration storedConfiguration, final String identifier) {
         final Map<PwmSetting,StoredValue> valueMap = makeValueMap(storedConfiguration, identifier, PROFILE_TYPE.getCategory());
-        return new ForgottenPasswordProfile(identifier, valueMap);
+        return new
+                ForgottenPasswordProfile(identifier, valueMap);
 
     }
 

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

@@ -40,9 +40,10 @@ import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.servlet.CommandServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.Validator;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.ws.server.RestResultBean;
@@ -183,7 +184,7 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
             throws PwmUnrecoverableException, IOException
     {
         String redirectURL = this.getContextPath() + PwmServletDefinition.Command.servletUrl();
-        redirectURL = PwmURL.appendAndEncodeUrlParameters(redirectURL, Collections.singletonMap(PwmConstants.PARAM_ACTION_REQUEST, "continue"));
+        redirectURL = PwmURL.appendAndEncodeUrlParameters(redirectURL, Collections.singletonMap(PwmConstants.PARAM_ACTION_REQUEST, CommandServlet.CommandAction.next.toString()));
         sendRedirect(redirectURL);
     }
 
@@ -415,12 +416,6 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         return true;
     }
 
-
-
-    public void setResponseError(final ErrorInformation errorInformation) {
-        setAttribute(PwmRequest.Attribute.PwmErrorInfo, errorInformation);
-    }
-
     public void setAttribute(final PwmRequest.Attribute name, final Serializable value) {
         this.getHttpServletRequest().setAttribute(name.toString(),value);
     }

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

@@ -125,7 +125,7 @@ public class PwmResponse extends PwmHttpResponseWrapper {
             LOGGER.trace(pwmSession, "skipping success page due to configuration setting.");
             final String redirectUrl = pwmRequest.getContextPath()
                     +  PwmServletDefinition.Command.servletUrl()
-                    + "?processAction=continue";
+                    + "?processAction=next";
             sendRedirect(redirectUrl);
             return;
         }
@@ -145,7 +145,7 @@ public class PwmResponse extends PwmHttpResponseWrapper {
     {
         LOGGER.error(pwmRequest.getSessionLabel(), errorInformation);
 
-        pwmRequest.setResponseError(errorInformation);
+        pwmRequest.setAttribute(PwmRequest.Attribute.PwmErrorInfo, errorInformation);
 
         if (JavaHelper.enumArrayContainsValue(flags, Flag.ForceLogout)) {
             LOGGER.debug(pwmRequest, "forcing logout due to error " + errorInformation.toDebugStr());

+ 17 - 2
src/main/java/password/pwm/http/bean/ChangePasswordBean.java

@@ -22,6 +22,7 @@
 
 package password.pwm.http.bean;
 
+import com.google.gson.annotations.SerializedName;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.ldap.PasswordChangeProgressChecker;
 
@@ -35,18 +36,32 @@ import java.util.Set;
  * @author Jason D. Rivard
  */
 public class ChangePasswordBean extends PwmSessionBean {
-// ------------------------------ FIELDS ------------------------------
 
-    // ------------------------- PUBLIC CONSTANTS -------------------------
+    @SerializedName("ap")
     private boolean agreementPassed;
+
+    @SerializedName("cpr")
     private boolean currentPasswordRequired;
+
+    @SerializedName("cpp")
     private boolean currentPasswordPassed;
+
+    @SerializedName("fp")
     private boolean formPassed;
+
+    @SerializedName("acp")
     private boolean allChecksPassed;
+
+    @SerializedName("n")
     private boolean nextAllowedTimePassed;
+
+    @SerializedName("wp")
     private boolean warnPassed;
 
+    @SerializedName("pt")
     private PasswordChangeProgressChecker.ProgressTracker changeProgressTracker;
+
+    @SerializedName("mc")
     private Instant changePasswordMaxCompletion;
 
 

+ 8 - 2
src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java

@@ -33,7 +33,7 @@ import password.pwm.config.option.SessionBeanMode;
 
 import java.io.Serializable;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -132,7 +132,7 @@ public class ForgottenPasswordBean extends PwmSessionBean {
         private boolean allPassed;
 
         @SerializedName("m")
-        private final Set<IdentityVerificationMethod> satisfiedMethods = new HashSet<>();
+        private final Set<IdentityVerificationMethod> satisfiedMethods = new LinkedHashSet<>();
 
         @SerializedName("c")
         private MessageSendMethod tokenSendChoice;
@@ -204,6 +204,12 @@ public class ForgottenPasswordBean extends PwmSessionBean {
         public void setRemoteRecoveryMethod(final VerificationMethodSystem remoteRecoveryMethod) {
             this.remoteRecoveryMethod = remoteRecoveryMethod;
         }
+
+        public void clearTokenSentStatus() {
+            this.setTokenSent(false);
+            this.setTokenSentAddress(null);
+            this.setTokenSendChoice(null);
+        }
     }
 
     public static class RecoveryFlags implements Serializable {

+ 1 - 1
src/main/java/password/pwm/http/filter/ConfigAccessFilter.java

@@ -203,8 +203,8 @@ public class ConfigAccessFilter extends AbstractPwmFilter {
                     pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
                     pwmApplication.getIntruderManager().mark(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmSession.getLabel());
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_PASSWORD_ONLY_BAD);
-                    pwmRequest.setResponseError(errorInformation);
                     updateLoginHistory(pwmRequest,pwmRequest.getUserInfoIfLoggedIn(), false);
+                    throw new PwmUnrecoverableException(errorInformation);
                 }
             }
         }

+ 19 - 0
src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java

@@ -34,6 +34,7 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSessionWrapper;
+import password.pwm.http.bean.PwmSessionBean;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.util.Validator;
 import password.pwm.util.logging.PwmLogger;
@@ -277,4 +278,22 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
         }
         throw new IllegalStateException("unable to determine PwmServletDefinition for class " + this.getClass().getName());
     }
+
+    protected void setLastError(final PwmRequest pwmRequest, final ErrorInformation errorInformation) throws PwmUnrecoverableException {
+        final Class<? extends PwmSessionBean> beanClass = this.getServletDefinition().getPwmSessionBeanClass();
+        if (beanClass != null) {
+            final PwmSessionBean pwmSessionBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, beanClass);
+            pwmSessionBean.setLastError(errorInformation);
+            pwmRequest.setAttribute(PwmRequest.Attribute.PwmErrorInfo, errorInformation);
+        }
+    }
+
+    protected 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.setAttribute(PwmRequest.Attribute.PwmErrorInfo, pwmSessionBean.getLastError());
+            pwmSessionBean.setLastError(null);
+        }
+    }
 }

+ 4 - 4
src/main/java/password/pwm/http/servlet/ActivateUserServlet.java

@@ -213,7 +213,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
         if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
             final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
             LOGGER.debug(pwmRequest, errorInfo);
-            pwmRequest.setResponseError(errorInfo);
+            setLastError(pwmRequest, errorInfo);
             return;
         }
 
@@ -272,7 +272,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
         } catch (PwmOperationalException e) {
             pwmApplication.getIntruderManager().convenience().markAttributes(formValues, pwmSession);
             pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             LOGGER.debug(pwmSession.getLabel(),e.getErrorInformation().toDebugStr());
         }
 
@@ -321,7 +321,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
                     final Locale locale = pwmSession.getSessionStateBean().getLocale();
                     initializeToken(pwmRequest, locale, activateUserBean.getUserIdentity());
                 } catch (PwmOperationalException e) {
-                    pwmRequest.setResponseError(e.getErrorInformation());
+                    setLastError(pwmRequest, e.getErrorInformation());
                     forwardToActivateUserForm(pwmRequest);
                     return;
                 }
@@ -704,7 +704,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
                 errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT);
             }
             LOGGER.debug(pwmSession.getLabel(), errorInformation.toDebugStr());
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
         }
 
         this.advanceToNextStage(pwmRequest);

+ 134 - 117
src/main/java/password/pwm/http/servlet/CommandServlet.java

@@ -29,10 +29,10 @@ import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
-import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpHeader;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
@@ -44,6 +44,8 @@ import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
 import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 
 /**
@@ -67,60 +69,48 @@ import java.util.Map;
 )
 
 
-public class CommandServlet extends AbstractPwmServlet {
+public class CommandServlet extends ControlledPwmServlet {
 // ------------------------------ FIELDS ------------------------------
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(CommandServlet.class);
 
     @Override
-    protected ProcessAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        return null;
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return CommandAction.class;
     }
 
-    public void processAction(
-            final PwmRequest pwmRequest
-    )
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
-    {
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-
-        String action = pwmRequest.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST);
-        if (action.isEmpty()) {
-            final String uri = pwmRequest.getHttpServletRequest().getRequestURI();
-            if (uri != null && !uri.toLowerCase().endsWith("command") && !uri.toLowerCase().endsWith("CommandServlet")) {
-                final int lastSlash = uri.lastIndexOf("/");
-                action = uri.substring(lastSlash + 1, uri.length());
-            }
+    public enum CommandAction implements ProcessAction {
+        idleUpdate,
+        checkResponses,
+        checkIfResponseConfigNeeded,  //deprecated
+        checkExpire,
+        checkProfile,
+        checkAttributes, // deprecated
+        checkAll,
+        pageLeaveNotice,
+        cspReport,
+        next,
+
+        ;
+
+        @Override
+        public Collection<HttpMethod> permittedMethods() {
+            return Arrays.asList(HttpMethod.GET, HttpMethod.POST);
         }
+    }
 
-        LOGGER.trace(pwmSession, "received request for action " + action);
-
-        if ("idleUpdate".equalsIgnoreCase(action)) {
-            processIdleUpdate(pwmRequest);
-        } else if ("checkResponses".equalsIgnoreCase(action) || "checkIfResponseConfigNeeded".equalsIgnoreCase(action)) {
-            CheckCommands.processCheckResponses(pwmRequest);
-        } else if ("checkExpire".equalsIgnoreCase(action)) {
-            CheckCommands.processCheckExpire(pwmRequest);
-        } else if ("checkProfile".equalsIgnoreCase(action) || "checkAttributes".equalsIgnoreCase(action)) {
-            CheckCommands.processCheckProfile(pwmRequest);
-        } else if ("checkAll".equalsIgnoreCase(action)) {
-            CheckCommands.processCheckAll(pwmRequest);
-        } else if ("continue".equalsIgnoreCase(action)) {
-            processContinue(pwmRequest);
-        } else if ("pageLeaveNotice".equalsIgnoreCase(action)) {
-            processPageLeaveNotice(pwmRequest);
-        } else if ("csp-report".equalsIgnoreCase(action)) {
-            processCspReport(pwmRequest);
-        } else {
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,"unknown command sent to CommandServlet: " + action);
-            LOGGER.debug(pwmSession, errorInformation);
-            pwmRequest.respondWithError(errorInformation);
-        }
+    @Override
+    protected void nextStep(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException {
+        // no mvc pattern in this servlet
     }
 
-    private static void processCspReport(
+    @Override
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+        return ProcessStatus.Continue;
+    }
+
+    @ActionHandler(action = "cspReport")
+    private ProcessStatus processCspReport(
             final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException
@@ -132,9 +122,11 @@ public class CommandServlet extends AbstractPwmServlet {
         } catch (Exception e) {
             LOGGER.error("error processing csp report: " + e.getMessage() + ", body=" + body);
         }
+        return ProcessStatus.Halt;
     }
 
-    private static void processIdleUpdate(
+    @ActionHandler(action = "idleUpdate")
+    private ProcessStatus processIdleUpdate(
             final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
@@ -144,11 +136,11 @@ public class CommandServlet extends AbstractPwmServlet {
             pwmRequest.getPwmResponse().setHeader(HttpHeader.Cache_Control, "no-cache, no-store, must-revalidate");
             pwmRequest.getPwmResponse().setContentType(PwmConstants.ContentTypeValue.plain);
         }
+        return ProcessStatus.Halt;
     }
 
-
-
-    private static void processContinue(
+    @ActionHandler(action = "next")
+    private ProcessStatus processContinue(
             final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException, ServletException
@@ -159,7 +151,7 @@ public class CommandServlet extends AbstractPwmServlet {
 
         if (pwmRequest.isAuthenticated()) {
             if (AuthenticationFilter.forceRequiredRedirects(pwmRequest) == ProcessStatus.Halt) {
-                return;
+                return ProcessStatus.Halt;
             }
 
             // log the user out if our finish action is currently set to log out.
@@ -167,15 +159,16 @@ public class CommandServlet extends AbstractPwmServlet {
             if (forceLogoutOnChange && pwmSession.getSessionStateBean().isPasswordModified()) {
                 LOGGER.trace(pwmSession, "logging out user; password has been modified");
                 pwmRequest.sendRedirect(PwmServletDefinition.Logout);
-                return;
+                return ProcessStatus.Halt;
             }
         }
 
         redirectToForwardURL(pwmRequest);
+        return ProcessStatus.Halt;
     }
 
-
-    private void processPageLeaveNotice(final PwmRequest pwmRequest)
+    @ActionHandler(action = "pageLeaveNotice")
+    private ProcessStatus processPageLeaveNotice(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
@@ -187,9 +180,98 @@ public class CommandServlet extends AbstractPwmServlet {
             pwmRequest.getPwmResponse().setHeader(HttpHeader.Cache_Control, "no-cache, no-store, must-revalidate");
             pwmRequest.getPwmResponse().setContentType(PwmConstants.ContentTypeValue.plain);
         }
+        return ProcessStatus.Halt;
     }
 
+    @ActionHandler(action = "checkAttributes")
+    private ProcessStatus processCheckAttributes(
+            final PwmRequest pwmRequest
+    )
+            throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
+    {
+        return processCheckProfile(pwmRequest);
+    }
 
+    @ActionHandler(action = "checkProfile")
+    private ProcessStatus processCheckProfile(
+            final PwmRequest pwmRequest
+    )
+            throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
+    {
+        if (!checkIfUserAuthenticated(pwmRequest)) {
+            return ProcessStatus.Halt;
+        }
+
+        if (pwmRequest.getPwmSession().getUserInfoBean().isRequiresUpdateProfile()) {
+            pwmRequest.sendRedirect(PwmServletDefinition.UpdateProfile);
+        } else {
+            redirectToForwardURL(pwmRequest);
+        }
+
+        return ProcessStatus.Halt;
+    }
+
+    @ActionHandler(action = "checkAll")
+    private ProcessStatus processCheckAll(
+            final PwmRequest pwmRequest
+    )
+            throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
+    {
+        if (!checkIfUserAuthenticated(pwmRequest)) {
+            return ProcessStatus.Halt;
+        }
+
+        if (AuthenticationFilter.forceRequiredRedirects(pwmRequest) == ProcessStatus.Continue) {
+            redirectToForwardURL(pwmRequest);
+        }
+        return ProcessStatus.Halt;
+    }
+
+    @ControlledPwmServlet.ActionHandler(action = "checkIfResponseConfigNeeded")
+    private ProcessStatus processCheckIfResponseConfigNeeded(
+            final PwmRequest pwmRequest
+    )
+            throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
+    {
+        return processCheckResponses(pwmRequest);
+    }
+
+    @ControlledPwmServlet.ActionHandler(action = "checkResponses")
+    private ProcessStatus processCheckResponses(
+            final PwmRequest pwmRequest
+    )
+            throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
+    {
+        if (!checkIfUserAuthenticated(pwmRequest)) {
+            return ProcessStatus.Halt;
+        }
+
+        if (pwmRequest.getPwmSession().getUserInfoBean().isRequiresResponseConfig()) {
+            pwmRequest.sendRedirect(PwmServletDefinition.SetupResponses);
+        } else {
+            redirectToForwardURL(pwmRequest);
+        }
+        return ProcessStatus.Halt;
+    }
+
+    @ControlledPwmServlet.ActionHandler(action = "checkExpire")
+    private ProcessStatus processCheckExpire(
+            final PwmRequest pwmRequest
+    )
+            throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
+    {
+        if (!checkIfUserAuthenticated(pwmRequest)) {
+            return ProcessStatus.Halt;
+        }
+
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        if (pwmSession.getUserInfoBean().isRequiresNewPassword() && !pwmSession.getLoginInfoBean().isLoginFlag(LoginInfoBean.LoginFlag.skipNewPw)) {
+            pwmRequest.sendRedirect(PwmServletDefinition.PrivateChangePassword.servletUrlName());
+        } else {
+            redirectToForwardURL(pwmRequest);
+        }
+        return ProcessStatus.Halt;
+    }
 
     private static void redirectToForwardURL(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException
@@ -222,70 +304,5 @@ public class CommandServlet extends AbstractPwmServlet {
         }
         return true;
     }
-
-    private static class CheckCommands {
-        private static void processCheckProfile(
-                final PwmRequest pwmRequest
-        )
-                throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
-        {
-            if (!checkIfUserAuthenticated(pwmRequest)) {
-                return;
-            }
-
-            if (pwmRequest.getPwmSession().getUserInfoBean().isRequiresUpdateProfile()) {
-                pwmRequest.sendRedirect(PwmServletDefinition.UpdateProfile);
-            } else {
-                redirectToForwardURL(pwmRequest);
-            }
-        }
-
-        private static void processCheckAll(
-                final PwmRequest pwmRequest
-        )
-                throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
-        {
-            if (!checkIfUserAuthenticated(pwmRequest)) {
-                return;
-            }
-
-            if (AuthenticationFilter.forceRequiredRedirects(pwmRequest) == ProcessStatus.Continue) {
-                redirectToForwardURL(pwmRequest);
-            }
-        }
-
-        private static void processCheckResponses(
-                final PwmRequest pwmRequest
-        )
-                throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
-        {
-            if (!checkIfUserAuthenticated(pwmRequest)) {
-                return;
-            }
-
-            if (pwmRequest.getPwmSession().getUserInfoBean().isRequiresResponseConfig()) {
-                pwmRequest.sendRedirect(PwmServletDefinition.SetupResponses);
-            } else {
-                redirectToForwardURL(pwmRequest);
-            }
-        }
-
-        private static void processCheckExpire(
-                final PwmRequest pwmRequest
-        )
-                throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
-        {
-            if (!checkIfUserAuthenticated(pwmRequest)) {
-                return;
-            }
-
-            final PwmSession pwmSession = pwmRequest.getPwmSession();
-            if (pwmSession.getUserInfoBean().isRequiresNewPassword() && !pwmSession.getLoginInfoBean().isLoginFlag(LoginInfoBean.LoginFlag.skipNewPw)) {
-                pwmRequest.sendRedirect(PwmServletDefinition.PrivateChangePassword.servletUrlName());
-            } else {
-                redirectToForwardURL(pwmRequest);
-            }
-        }
-    }
 }
 

+ 0 - 19
src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java

@@ -30,7 +30,6 @@ 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.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 
@@ -185,23 +184,5 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         }
         return interestedMethod;
     }
-
-    protected void setLastError(final PwmRequest pwmRequest, final ErrorInformation errorInformation) throws PwmUnrecoverableException {
-        final Class<? extends PwmSessionBean> beanClass = this.getServletDefinition().getPwmSessionBeanClass();
-        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 {
-        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);
-        }
-    }
 }
 

+ 43 - 53
src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java

@@ -40,12 +40,12 @@ 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.bean.DeleteAccountBean;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.ActionExecutor;
@@ -65,7 +65,7 @@ import java.util.Locale;
                 PwmConstants.URL_PREFIX_PRIVATE + "/DeleteAccount"
         }
 )
-public class DeleteAccountServlet extends AbstractPwmServlet {
+public class DeleteAccountServlet extends ControlledPwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(DeleteAccountServlet.class);
 
@@ -89,62 +89,45 @@ public class DeleteAccountServlet extends AbstractPwmServlet {
         }
     }
 
-    protected DeleteAccountAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return DeleteAccountAction.class;
+    }
 
-    {
-        try {
-            return DeleteAccountAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    private DeleteAccountProfile getProfile(final PwmRequest pwmRequest) {
+        return pwmRequest.getPwmSession().getSessionManager().getSelfDeleteProfile(pwmRequest.getPwmApplication());
+    }
+
+    private DeleteAccountBean getBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+        return pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, DeleteAccountBean.class);
     }
 
     @Override
-    protected void processAction(final PwmRequest pwmRequest)
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
-    {
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final DeleteAccountProfile deleteAccountProfile = pwmRequest.getPwmSession().getSessionManager().getSelfDeleteProfile(pwmApplication);
-        final DeleteAccountBean deleteAccountBean = pwmApplication.getSessionStateService().getBean(pwmRequest, DeleteAccountBean.class);
+        final DeleteAccountProfile deleteAccountProfile = getProfile(pwmRequest);
 
         if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.DELETE_ACCOUNT_ENABLE)) {
-            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "Setting " + PwmSetting.DELETE_ACCOUNT_ENABLE.toMenuLocationDebug(null,null) + " is not enabled."));
-            return;
+            throw new PwmUnrecoverableException(new ErrorInformation(
+                    PwmError.ERROR_SERVICE_NOT_AVAILABLE,
+                    "Setting " +
+                            PwmSetting.DELETE_ACCOUNT_ENABLE.toMenuLocationDebug(null,null) + " is not enabled.")
+            );
         }
 
         if (deleteAccountProfile == null) {
-            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_NO_PROFILE_ASSIGNED));
-            return;
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NO_PROFILE_ASSIGNED));
         }
 
-        final DeleteAccountAction action = readProcessAction(pwmRequest);
-        if (action != null) {
-            switch (action) {
-                case agree:
-                    handleAgreeRequest(pwmRequest, deleteAccountBean);
-                    break;
-
-                case reset:
-                    handleResetRequest(pwmRequest);
-                    return;
-
-                case delete:
-                    handleDeleteRequest(pwmRequest, deleteAccountProfile);
-                    return;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(action);
-
-            }
-        }
-
-        advancedToNextStage(pwmRequest, deleteAccountProfile, deleteAccountBean);
+        return ProcessStatus.Continue;
     }
 
-    private void advancedToNextStage(final PwmRequest pwmRequest, final DeleteAccountProfile profile, final DeleteAccountBean bean)
-            throws PwmUnrecoverableException, ServletException, IOException
+    @Override
+    protected void nextStep(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
     {
+        final DeleteAccountProfile profile = getProfile(pwmRequest);
+        final DeleteAccountBean bean = getBean(pwmRequest);
 
         final String selfDeleteAgreementText = profile.readSettingAsLocalizedString(
                 PwmSetting.DELETE_ACCOUNT_AGREEMENT,
@@ -164,23 +147,26 @@ public class DeleteAccountServlet extends AbstractPwmServlet {
         pwmRequest.forwardToJsp(JspUrl.SELF_DELETE_CONFIRM);
     }
 
-    private void handleResetRequest(
+    @ActionHandler(action = "reset")
+    private ProcessStatus handleResetRequest(
             final PwmRequest pwmRequest
     )
             throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
         pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, DeleteAccountBean.class);
         pwmRequest.sendRedirectToContinue();
+        return ProcessStatus.Halt;
     }
 
-    private void handleAgreeRequest(
-            final PwmRequest pwmRequest,
-            final DeleteAccountBean deleteAccountBean
+    @ActionHandler(action = "agree")
+    private ProcessStatus handleAgreeRequest(
+            final PwmRequest pwmRequest
     )
             throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
         LOGGER.debug(pwmRequest, "user accepted agreement");
 
+        final DeleteAccountBean deleteAccountBean = getBean(pwmRequest);
         if (!deleteAccountBean.isAgreementPassed()) {
             deleteAccountBean.setAgreementPassed(true);
             final AuditRecord auditRecord = new AuditRecordFactory(pwmRequest).createUserAuditRecord(
@@ -191,15 +177,18 @@ public class DeleteAccountServlet extends AbstractPwmServlet {
             );
             pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
         }
+
+        return ProcessStatus.Continue;
     }
 
-    private void handleDeleteRequest(
-            final PwmRequest pwmRequest,
-            final DeleteAccountProfile profile
+    @ActionHandler(action = "delete")
+    private ProcessStatus handleDeleteRequest(
+            final PwmRequest pwmRequest
     )
-            throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException {
+            throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
+    {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final DeleteAccountProfile deleteAccountProfile = pwmRequest.getPwmSession().getSessionManager().getSelfDeleteProfile(pwmApplication);
+        final DeleteAccountProfile deleteAccountProfile = getProfile(pwmRequest);
         final UserIdentity userIdentity = pwmRequest.getUserInfoIfLoggedIn();
 
 
@@ -229,7 +218,7 @@ public class DeleteAccountServlet extends AbstractPwmServlet {
         // mark the event log
         pwmApplication.getAuditManager().submit(AuditEvent.DELETE_ACCOUNT, pwmRequest.getPwmSession().getUserInfoBean(), pwmRequest.getPwmSession());
 
-        final String nextUrl = profile.readSettingAsString(PwmSetting.DELETE_ACCOUNT_NEXT_URL);
+        final String nextUrl = deleteAccountProfile.readSettingAsString(PwmSetting.DELETE_ACCOUNT_NEXT_URL);
         if (nextUrl != null && !nextUrl.isEmpty()) {
             final MacroMachine macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine(pwmApplication);
             final String macroedUrl = macroMachine.expandMacros(nextUrl);
@@ -255,6 +244,7 @@ public class DeleteAccountServlet extends AbstractPwmServlet {
         // delete finished, so logout and redirect.
         pwmRequest.getPwmSession().unauthenticateUser(pwmRequest);
         pwmRequest.sendRedirectToContinue();
+        return ProcessStatus.Halt;
     }
 
     private static void sendProfileUpdateEmailNotice(

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

@@ -133,7 +133,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet {
         if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
             final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
             LOGGER.debug(pwmRequest, errorInfo);
-            pwmRequest.setResponseError(errorInfo);
+            setLastError(pwmRequest, errorInfo);
             forwardToFormJsp(pwmRequest);
             return;
         }
@@ -180,7 +180,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet {
             if (userIdentity == null) {
                 pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
                 pwmApplication.getStatisticsManager().incrementValue(Statistic.FORGOTTEN_USERNAME_FAILURES);
-                pwmRequest.setResponseError(PwmError.ERROR_CANT_MATCH_USER.toInfo());
+                setLastError(pwmRequest, PwmError.ERROR_CANT_MATCH_USER.toInfo());
                 forwardToFormJsp(pwmRequest);
                 return;
             }
@@ -209,7 +209,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet {
                     ? new ErrorInformation(PwmError.ERROR_CANT_MATCH_USER,e.getErrorInformation().getDetailedErrorMsg(),
                     e.getErrorInformation().getFieldValues())
                     : e.getErrorInformation();
-            pwmRequest.setResponseError(errorInfo);
+            setLastError(pwmRequest, errorInfo);
             pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
             pwmApplication.getIntruderManager().convenience().markAttributes(formValues, pwmSession);
         }

+ 6 - 6
src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java

@@ -259,11 +259,11 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
             return;
         } catch (PwmOperationalException e) {
             LOGGER.error(pwmSession, e.getErrorInformation().toDebugStr());
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
         } catch (ChaiOperationException e) {
             final ErrorInformation info = new ErrorInformation(PwmError.ERROR_UNKNOWN, "unexpected error writing to ldap: " + e.getMessage());
             LOGGER.error(pwmSession, info);
-            pwmRequest.setResponseError(info);
+            setLastError(pwmRequest, info);
         }
         this.forwardToUpdateJSP(pwmRequest, guestRegistrationBean);
     }
@@ -330,7 +330,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
                     if (origAdminDn != null && origAdminDn.length() > 0) {
                         if (!pwmSession.getUserInfoBean().getUserIdentity().getUserDN().equalsIgnoreCase(origAdminDn)) {
                             final ErrorInformation info = new ErrorInformation(PwmError.ERROR_ORIG_ADMIN_ONLY);
-                            pwmRequest.setResponseError(info);
+                            setLastError(pwmRequest, info);
                             LOGGER.warn(pwmSession, info);
                             this.forwardToJSP(pwmRequest, guestRegistrationBean);
                         }
@@ -361,7 +361,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
             }
         } catch (PwmOperationalException e) {
             final ErrorInformation error = e.getErrorInformation();
-            pwmRequest.setResponseError(error);
+            setLastError(pwmRequest, error);
             this.forwardToJSP(pwmRequest, guestRegistrationBean);
             return;
         }
@@ -469,12 +469,12 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
             pwmRequest.getPwmResponse().forwardToSuccessPage(Message.Success_CreateGuest);
         } catch (ChaiOperationException e) {
             final ErrorInformation info = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE, "error creating user: " + e.getMessage());
-            pwmRequest.setResponseError(info);
+            setLastError(pwmRequest, info);
             LOGGER.warn(pwmSession, info);
             this.forwardToJSP(pwmRequest, guestRegistrationBean);
         } catch (PwmOperationalException e) {
             LOGGER.error(pwmSession, e.getErrorInformation().toDebugStr());
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             this.forwardToJSP(pwmRequest, guestRegistrationBean);
         }
     }

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

@@ -122,7 +122,7 @@ public class LoginServlet extends ControlledPwmServlet {
         try {
             handleLoginRequest(pwmRequest, valueMap, passwordOnly);
         } catch (PwmOperationalException e) {
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             forwardToJSP(pwmRequest, passwordOnly);
             return ProcessStatus.Halt;
         }

+ 4 - 4
src/main/java/password/pwm/http/servlet/SetupOtpServlet.java

@@ -241,7 +241,7 @@ public class SetupOtpServlet extends AbstractPwmServlet {
                     errorInformation = new ErrorInformation(PwmError.ERROR_WRITING_OTP_SECRET,"unexpected error saving otp secret: " + e.getMessage());
                 }
                 LOGGER.error(pwmSession, errorInformation.toDebugStr());
-                pwmRequest.setResponseError(errorInformation);
+                setLastError(pwmRequest, errorInformation);
             }
         }
 
@@ -347,7 +347,7 @@ public class SetupOtpServlet extends AbstractPwmServlet {
         try {
             service.clearOTPUserConfiguration(pwmSession, theUser);
         } catch (PwmOperationalException e) {
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             LOGGER.error(pwmRequest, e.getErrorInformation());
             return;
         }
@@ -385,11 +385,11 @@ public class SetupOtpServlet extends AbstractPwmServlet {
                     otpBean.setChallenge(null);
                 } else {
                     LOGGER.debug(pwmRequest, "test OTP token returned false, incorrect OTP secret provided");
-                            pwmRequest.setResponseError(new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT));
+                    setLastError(pwmRequest, new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT));
                 }
             } catch (PwmOperationalException e) {
                 LOGGER.error(pwmRequest, "error validating otp token: " + e.getMessage());
-                pwmRequest.setResponseError(e.getErrorInformation());
+                setLastError(pwmRequest, e.getErrorInformation());
             }
         }
 

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

@@ -400,6 +400,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
 
 
 
+
     final Map<FormConfiguration,String> readFormParametersFromRequest(
             final PwmRequest pwmRequest,
             final UpdateAttributesProfile updateAttributesProfile,
@@ -412,10 +413,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
         //read the values from the request
         final Map<FormConfiguration,String> formValueMap = FormUtility.readFormValuesFromRequest(pwmRequest, formFields, pwmRequest.getLocale());
 
-        updateProfileBean.getFormData().clear();
-        updateProfileBean.getFormData().putAll(FormUtility.asStringMap(formValueMap));
-
-        return formValueMap;
+        return updateBeanFormData(formFields, formValueMap, updateProfileBean);
     }
 
     static Map<FormConfiguration,String> readFromJsonRequest(
@@ -429,10 +427,28 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
 
         final Map<FormConfiguration,String> formValueMap = FormUtility.readFormValuesFromMap(pwmRequest.readBodyAsJsonStringMap(), formFields, pwmRequest.getLocale());
 
+        return updateBeanFormData(formFields, formValueMap, updateProfileBean);
+    }
+
+    static Map<FormConfiguration,String> updateBeanFormData(
+            final List<FormConfiguration> formFields,
+            final Map<FormConfiguration,String> formValueMap,
+            final UpdateProfileBean updateProfileBean
+    ) {
+        final LinkedHashMap<FormConfiguration,String> newFormValueMap = new LinkedHashMap<>();
+        for (final FormConfiguration formConfiguration : formFields) {
+            if (formConfiguration.isReadonly()) {
+                final String existingValue = updateProfileBean.getFormData().get(formConfiguration.getName());
+                newFormValueMap.put(formConfiguration, existingValue);
+            } else {
+                newFormValueMap.put(formConfiguration, formValueMap.get(formConfiguration));
+            }
+        }
+
         updateProfileBean.getFormData().clear();
-        updateProfileBean.getFormData().putAll(FormUtility.asStringMap(formValueMap));
+        updateProfileBean.getFormData().putAll(FormUtility.asStringMap(newFormValueMap));
 
-        return formValueMap;
+        return newFormValueMap;
     }
 
 

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

@@ -168,7 +168,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
             final PwmPasswordRuleValidator pwmPasswordRuleValidator = new PwmPasswordRuleValidator(pwmRequest.getPwmApplication(), uiBean.getPasswordPolicy());
             pwmPasswordRuleValidator.testPassword(password1,null,uiBean,theUser);
         } catch (PwmDataValidationException e) {
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             LOGGER.debug(pwmRequest, "failed password validation check: " + e.getErrorInformation().toDebugStr());
             return ProcessStatus.Continue;
         }
@@ -178,7 +178,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
                 PwmPasswordRule.CaseSensitive);
         if (PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH != PasswordUtility.figureMatchStatus(caseSensitive,
                 password1, password2)) {
-            pwmRequest.setResponseError(PwmError.PASSWORD_DOESNOTMATCH.toInfo());
+            setLastError(pwmRequest, PwmError.PASSWORD_DOESNOTMATCH.toInfo());
             pwmRequest.forwardToJsp(JspUrl.PASSWORD_CHANGE);
             return ProcessStatus.Continue;
         }
@@ -187,7 +187,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
             executeChangePassword(pwmRequest, password1);
         } catch (PwmOperationalException e) {
             LOGGER.debug(e.getErrorInformation().toDebugStr());
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             return ProcessStatus.Halt;
         }
 

+ 56 - 7
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -88,6 +88,7 @@ import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
 import password.pwm.util.CaptchaUtility;
+import password.pwm.util.LocaleHelper;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PostChangePasswordAction;
 import password.pwm.util.RandomPasswordGenerator;
@@ -100,6 +101,7 @@ import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.cr.NMASCrOperator;
 import password.pwm.util.operations.otp.OTPUserRecord;
 import password.pwm.ws.client.rest.RestTokenDataClient;
+import password.pwm.ws.server.RestResultBean;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -109,8 +111,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -151,6 +151,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
         verificationChoice(HttpMethod.POST),
         enterRemoteResponse(HttpMethod.POST),
         oauthReturn(HttpMethod.GET),
+        resendToken(HttpMethod.POST),
 
         ;
 
@@ -224,6 +225,13 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
 
+        final boolean resendEnabled = Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.TOKEN_RESEND_ENABLED));
+
+        if (resendEnabled) {
+            // clear token dest info in case we got here from a user 'go-back' request
+            forgottenPasswordBean.getProgress().clearTokenSentStatus();
+        }
+
         if (forgottenPasswordBean.getProgress().isAllPassed()) {
             final String choice = pwmRequest.readParameterAsString("choice");
 
@@ -657,6 +665,47 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
         return ProcessStatus.Continue;
     }
 
+    @ActionHandler(action = "resendToken")
+    private ProcessStatus processResendToken(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException, IOException
+    {
+        {
+            final boolean resendEnabled = Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.TOKEN_RESEND_ENABLED));
+            if (!resendEnabled) {
+                final String errorMsg = "token resend is not enabled";
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg);
+                throw new PwmUnrecoverableException(errorInformation);
+            }
+        }
+
+        final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
+
+        if (!forgottenPasswordBean.getProgress().isTokenSent()) {
+            final String errorMsg = "attempt to resend token, but initial token has not yet been sent";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg);
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+
+        {
+            LOGGER.trace(pwmRequest, "preparing to send a new token to user");
+            final long delayTime = Long.parseLong(pwmRequest.getConfig().readAppProperty(AppProperty.TOKEN_RESEND_DELAY_MS));
+            JavaHelper.pause(delayTime);
+        }
+
+        {
+            final UserInfoBean userInfoBean = readUserInfoBean(pwmRequest, forgottenPasswordBean);
+            final MessageSendMethod tokenSendMethod = forgottenPasswordBean.getProgress().getTokenSendChoice();
+            initializeAndSendToken(pwmRequest, userInfoBean, tokenSendMethod);
+        }
+
+        final RestResultBean restResultBean = new RestResultBean();
+        restResultBean.setSuccessMessage(LocaleHelper.getLocalizedMessage(Message.Success_TokenResend, pwmRequest));
+        pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
+    }
+
+
+
     @ActionHandler(action = "checkAttributes")
     private ProcessStatus processCheckAttributes(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, IOException, ServletException, PwmUnrecoverableException
@@ -1050,7 +1099,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
     {
         final Configuration config = pwmRequest.getConfig();
         final UserIdentity userIdentity = userInfoBean.getUserIdentity();
-        final Map<String,String> tokenMapData = new HashMap<>();
+        final Map<String,String> tokenMapData = new LinkedHashMap<>();
 
 
         try {
@@ -1084,7 +1133,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
                 pwmRequest.getLocale());
 
         final String tokenDestinationAddress = outputDestrestTokenDataClient.getDisplayValue();
-        final Set<String> destinationValues = new HashSet<>();
+        final Set<String> destinationValues = new LinkedHashSet<>();
         if (outputDestrestTokenDataClient.getEmail() != null) {
             destinationValues.add(outputDestrestTokenDataClient.getEmail());
         }
@@ -1478,7 +1527,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
             final ForgottenPasswordBean.RecoveryFlags recoveryFlags,
             final ForgottenPasswordBean.Progress progress)
     {
-        final Set<IdentityVerificationMethod> result = new HashSet<>();
+        final Set<IdentityVerificationMethod> result = new LinkedHashSet<>();
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.retainAll(progress.getSatisfiedMethods());
         return Collections.unmodifiableSet(result);
@@ -1491,11 +1540,11 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
     {
         final ForgottenPasswordBean.RecoveryFlags recoveryFlags = forgottenPasswordBean.getRecoveryFlags();
         final ForgottenPasswordBean.Progress progress = forgottenPasswordBean.getProgress();
-        final Set<IdentityVerificationMethod> result = new HashSet<>();
+        final Set<IdentityVerificationMethod> result = new LinkedHashSet<>();
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.removeAll(progress.getSatisfiedMethods());
 
-        for (final IdentityVerificationMethod recoveryVerificationMethods : new HashSet<>(result)) {
+        for (final IdentityVerificationMethod recoveryVerificationMethods : new LinkedHashSet<>(result)) {
             try {
                 verifyRequirementsForAuthMethod(pwmRequest, forgottenPasswordBean, recoveryVerificationMethods);
             } catch (PwmUnrecoverableException e) {

+ 2 - 2
src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java

@@ -183,7 +183,7 @@ public class OAuthConsumerServlet extends AbstractPwmServlet {
             } catch (PwmUnrecoverableException e) {
                 final String errorMsg = "unexpected error redirecting user to oauth page: " + e.toString();
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR, errorMsg);
-                pwmRequest.setResponseError(errorInformation);
+                setLastError(pwmRequest, errorInformation);
                 LOGGER.error(errorInformation.toDebugStr());
             }
         }
@@ -202,7 +202,7 @@ public class OAuthConsumerServlet extends AbstractPwmServlet {
             } else {
                 errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
             }
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
             LOGGER.error(errorInformation.toDebugStr());
             return;
         }

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

@@ -132,8 +132,7 @@ public class OAuthMachine {
         } catch (PwmUnrecoverableException e) {
             final String errorMsg = "unexpected error redirecting user to oauth page: " + e.toString();
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
-            pwmRequest.setResponseError(errorInformation);
-            LOGGER.error(errorInformation.toDebugStr());
+            throw new PwmUnrecoverableException(errorInformation);
         }
     }
 

+ 43 - 83
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -23,8 +23,6 @@
 package password.pwm.http.servlet.peoplesearch;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import password.pwm.Permission;
-import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.PwmSetting;
@@ -35,13 +33,13 @@ 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.PwmRequestFlag;
-import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.ws.server.RestResultBean;
@@ -56,7 +54,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-public abstract class PeopleSearchServlet extends AbstractPwmServlet {
+public abstract class PeopleSearchServlet extends ControlledPwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(PeopleSearchServlet.class);
 
@@ -82,69 +80,13 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
         }
     }
 
-    protected PeopleSearchActions readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException {
-        try {
-            return PeopleSearchActions.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return PeopleSearchActions.class;
     }
 
     @Override
-    protected void processAction(
-            final PwmRequest pwmRequest
-    )
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
-    {
-        if (!pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE)) {
-            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE));
-            return;
-        }
-
-        if (pwmRequest.getURL().isPublicUrl()) {
-            if (!pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC)) {
-                pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,"public peoplesearch service is not enabled"));
-                return;
-            }
-        } else {
-            if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PEOPLE_SEARCH)) {
-                pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED));
-                return;
-            }
-
-        }
-
-        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration(pwmRequest.getConfig());
-
-        final PeopleSearchActions peopleSearchAction = this.readProcessAction(pwmRequest);
-        if (peopleSearchAction != null) {
-            switch (peopleSearchAction) {
-                case search:
-                    restSearchRequest(pwmRequest);
-                    return;
-
-                case detail:
-                    restUserDetailRequest(pwmRequest);
-                    return;
-
-                case photo:
-                    processUserPhotoImageRequest(pwmRequest);
-                    return;
-
-                case clientData:
-                    restLoadClientData(pwmRequest, peopleSearchConfiguration);
-                    return;
-
-                case orgChartData:
-                    restOrgChartData(pwmRequest, peopleSearchConfiguration);
-                    return;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(peopleSearchAction);
-            }
-        }
-
+    protected void nextStep(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException {
         if (pwmRequest.getURL().isPublicUrl()) {
             pwmRequest.setFlag(PwmRequestFlag.HIDE_IDLE, true);
             pwmRequest.setFlag(PwmRequestFlag.NO_IDLE_TIMEOUT, true);
@@ -152,13 +94,23 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
         pwmRequest.forwardToJsp(JspUrl.PEOPLE_SEARCH);
     }
 
-    private void restLoadClientData(
-            final PwmRequest pwmRequest,
-            final PeopleSearchConfiguration peopleSearchConfiguration
+    @Override
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+        if (!pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE)) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE));
+        }
+
+        return ProcessStatus.Continue;
+    }
+
+    @ActionHandler(action = "clientData")
+    private ProcessStatus restLoadClientData(
+            final PwmRequest pwmRequest
 
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
+        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration(pwmRequest.getConfig());
 
         final Map<String, String> searchColumns = new LinkedHashMap<>();
         final List<FormConfiguration> searchForm = pwmRequest.getConfig().readSettingAsForm(PwmSetting.PEOPLE_SEARCH_RESULT_FORM);
@@ -175,10 +127,11 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
         final RestResultBean restResultBean = new RestResultBean(peopleSearchClientConfigBean);
         LOGGER.trace(pwmRequest, "returning clientData: " + JsonUtil.serialize(restResultBean));
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-
-    private void restSearchRequest(
+    @ActionHandler(action = "search")
+    private ProcessStatus restSearchRequest(
             final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
@@ -196,16 +149,17 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
         pwmRequest.outputJsonResult(restResultBean);
 
         LOGGER.trace(pwmRequest, "returning " + searchResultBean.getSearchResults().size() + " results for search request '" + username + "'");
+        return ProcessStatus.Halt;
     }
 
-
-
-    private void restOrgChartData(
-            final PwmRequest pwmRequest,
-            final PeopleSearchConfiguration peopleSearchConfiguration
+    @ActionHandler(action = "orgChartData")
+    private ProcessStatus restOrgChartData(
+            final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
+        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration(pwmRequest.getConfig());
+
         if (!peopleSearchConfiguration.isOrgChartEnabled()) {
             throw new PwmUnrecoverableException(PwmError.ERROR_SERVICE_NOT_AVAILABLE);
         }
@@ -216,7 +170,7 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
             if (userKey == null || userKey.isEmpty()) {
                 userIdentity = pwmRequest.getUserInfoIfLoggedIn();
                 if (userIdentity == null) {
-                    return;
+                    return ProcessStatus.Halt;
                 }
             } else {
                 userIdentity = UserIdentity.fromObfuscatedKey(userKey, pwmRequest.getPwmApplication());
@@ -236,17 +190,19 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
             LOGGER.error(pwmRequest, "error generating user detail object: " + e.getMessage());
             pwmRequest.respondWithError(e.getErrorInformation());
         }
-    }
 
+        return ProcessStatus.Halt;
+    }
 
-    private void restUserDetailRequest(
+    @ActionHandler(action = "detail")
+    private ProcessStatus restUserDetailRequest(
             final PwmRequest pwmRequest
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
         final String userKey = pwmRequest.readParameterAsString(PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey == null || userKey.isEmpty()) {
-            return;
+            return ProcessStatus.Halt;
         }
 
         try {
@@ -260,9 +216,12 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
             LOGGER.error(pwmRequest, "error generating user detail object: " + e.getMessage());
             pwmRequest.respondWithError(e.getErrorInformation());
         }
+
+        return ProcessStatus.Halt;
     }
 
-    private void processUserPhotoImageRequest(final PwmRequest pwmRequest)
+    @ActionHandler(action = "photo")
+    private ProcessStatus processUserPhotoImageRequest(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
         final String userKey = pwmRequest.readParameterAsString(PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation);
@@ -270,7 +229,7 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, PARAM_USERKEY + " parameter is missing");
             LOGGER.error(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
-            return;
+            return ProcessStatus.Halt;
         }
 
 
@@ -282,7 +241,7 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, "error during photo request while checking if requested userIdentity is within search scope: " + e.getMessage());
             LOGGER.error(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
-            return;
+            return ProcessStatus.Halt;
         }
 
         LOGGER.debug(pwmRequest, "received user photo request to view user " + userIdentity.toString());
@@ -294,7 +253,7 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
             final ErrorInformation errorInformation = e.getErrorInformation();
             LOGGER.error(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
-            return;
+            return ProcessStatus.Halt;
         }
 
         addExpiresHeadersToResponse(pwmRequest);
@@ -305,6 +264,7 @@ public abstract class PeopleSearchServlet extends AbstractPwmServlet {
 
             outputStream.write(photoData.getContents());
         }
+        return ProcessStatus.Halt;
     }
 
     private void addExpiresHeadersToResponse(final PwmRequest pwmRequest) {

+ 18 - 0
src/main/java/password/pwm/http/servlet/peoplesearch/PrivatePeopleSearchServlet.java

@@ -22,9 +22,18 @@
 
 package password.pwm.http.servlet.peoplesearch;
 
+
+import password.pwm.Permission;
 import password.pwm.PwmConstants;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ProcessStatus;
+import password.pwm.http.PwmRequest;
 
+import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
+import java.io.IOException;
 
 @WebServlet(
         name="PrivatePeopleSearchServlet",
@@ -37,4 +46,13 @@ import javax.servlet.annotation.WebServlet;
         }
 )
 public class PrivatePeopleSearchServlet extends PeopleSearchServlet {
+
+        @Override
+        public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+                if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PEOPLE_SEARCH)) {
+                        throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED));
+                }
+
+                return super.preProcessCheck(pwmRequest);
+        }
 }

+ 20 - 0
src/main/java/password/pwm/http/servlet/peoplesearch/PublicPeopleSearchServlet.java

@@ -23,8 +23,16 @@
 package password.pwm.http.servlet.peoplesearch;
 
 import password.pwm.PwmConstants;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ProcessStatus;
+import password.pwm.http.PwmRequest;
 
+import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
+import java.io.IOException;
 
 @WebServlet(
         name="PublicPeopleSearchServlet",
@@ -37,4 +45,16 @@ import javax.servlet.annotation.WebServlet;
         }
 )
 public class PublicPeopleSearchServlet extends PeopleSearchServlet {
+
+        @Override
+        public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+                if (!pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC)) {
+                        throw new PwmUnrecoverableException(new ErrorInformation(
+                                PwmError.ERROR_SERVICE_NOT_AVAILABLE,
+                                "public peoplesearch service is not enabled")
+                        );
+                }
+
+                return super.preProcessCheck(pwmRequest);
+        }
 }

+ 15 - 13
src/main/java/password/pwm/http/tag/ErrorMessageTag.java

@@ -60,36 +60,38 @@ public class ErrorMessageTag extends PwmAbstractTag {
                 pwmApplication = ContextManager.getPwmApplication(pageContext.getSession());
             } catch (PwmException e) { /* noop */ }
 
+            if (pwmRequest == null || pwmApplication == null) {
+                return EVAL_PAGE;
+            }
+
             final ErrorInformation error = (ErrorInformation)pwmRequest.getAttribute(PwmRequest.Attribute.PwmErrorInfo);
 
             if (error != null) {
+                final boolean allowHtml = Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.HTTP_ERRORS_ALLOW_HTML));
                 final boolean showErrorDetail = pwmApplication.determineIfDetailErrorMsgShown();
 
-                String outputMsg;
-                if (showErrorDetail) {
-                    final String errorDetail = error.toDebugStr() == null ? "" : " { " + error.toDebugStr() + " }";
-                    outputMsg = error.toUserStr(pwmRequest.getPwmSession(), pwmApplication) + errorDetail;
-                }  else {
-                    outputMsg = error.toUserStr(pwmRequest.getPwmSession(), pwmApplication);
-                }
-
-                final boolean allowHtml = pwmApplication != null && Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_HEADER_SEND_XVERSION));
+                String outputMsg = error.toUserStr(pwmRequest.getPwmSession(), pwmApplication);
                 if (!allowHtml) {
                     outputMsg = StringUtil.escapeHtml(outputMsg);
                 }
 
+                if (showErrorDetail) {
+                    final String errorDetail = error.toDebugStr() == null ? "" : " { " + error.toDebugStr() + " }";
+                    // detail should always be escaped - it may contain untrusted data
+                    outputMsg += StringUtil.escapeHtml(errorDetail);
+                }
+
                 outputMsg = outputMsg.replace("\n","<br/>");
 
-                if (pwmRequest != null) {
-                    final MacroMachine macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine(pwmApplication);
-                    outputMsg = macroMachine.expandMacros(outputMsg);
-                }
+                final MacroMachine macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine(pwmApplication);
+                outputMsg = macroMachine.expandMacros(outputMsg);
 
                 pageContext.getOut().write(outputMsg);
             }
         } catch (PwmUnrecoverableException e) {
             /* app not running */
         } catch (Exception e) {
+            LOGGER.error("error executing error message tag: " + e.getMessage(), e);
             throw new JspTagException(e.getMessage());
         }
         return EVAL_PAGE;

+ 4 - 0
src/main/java/password/pwm/http/tag/PwmFormIDTag.java

@@ -42,6 +42,10 @@ public class PwmFormIDTag extends TagSupport {
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmFormIDTag.class);
 
     private static String buildPwmFormID(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+        if (pwmRequest == null || pwmRequest.getPwmApplication() == null) {
+            return "";
+        }
+
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         if (pwmApplication == null) {
             return "";

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

@@ -44,6 +44,7 @@ public enum PwmIfTest {
     endUserFunctionalityAvaiable(new EndUserFunctionalityTest()),
     showIcons(new BooleanAppPropertyTest(AppProperty.CLIENT_JSP_SHOW_ICONS)),
     showCancel(new BooleanPwmSettingTest(PwmSetting.DISPLAY_CANCEL_BUTTON)),
+    maskTokenInput(new BooleanPwmSettingTest(PwmSetting.DISPLAY_MASK_TOKEN_FIELDS)),
     showHome(new BooleanPwmSettingTest(PwmSetting.DISPLAY_HOME_BUTTON)),
     showLogout(new BooleanPwmSettingTest(PwmSetting.DISPLAY_LOGOUT_BUTTON)),
     showLoginOptions(new BooleanPwmSettingTest(PwmSetting.DISPLAY_LOGIN_PAGE_OPTIONS)),

+ 1 - 0
src/main/java/password/pwm/i18n/Message.java

@@ -54,6 +54,7 @@ public enum Message implements PwmDisplayBundle {
     Success_PasswordSend(null),
     Success_Action(null),
     Success_OtpSetup(null),
+    Success_TokenResend(null),
 
     EventLog_Startup(null),
     EventLog_Shutdown(null),

+ 1 - 1
src/main/java/password/pwm/svc/wordlist/Populator.java

@@ -133,7 +133,7 @@ class Populator {
 
         perReportStats = new PopulationStats();
         return rootWordlist.DEBUG_LABEL + ", lines/second="
-                + lps + ", line=" + overallStats.getLines() + ")"
+                + lps + ", line=" + overallStats.getLines() + ""
                 + " current zipEntry=" + zipFileReader.currentZipName();
     }
 

+ 0 - 38
src/main/java/password/pwm/util/macro/InternalMacros.java

@@ -25,10 +25,8 @@ package password.pwm.util.macro;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmEnvironment;
-import password.pwm.bean.UserInfoBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.http.ContextManager;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Collections;
@@ -44,8 +42,6 @@ public abstract class InternalMacros {
 
     static {
         final Map<Class<? extends MacroImplementation>,MacroImplementation.Scope>  defaultMacros = new HashMap<>();
-        defaultMacros.put(OtpSetupTimeMacro.class, MacroImplementation.Scope.Static);
-        defaultMacros.put(ResponseSetupTimeMacro.class, MacroImplementation.Scope.Static);
         defaultMacros.put(PwmSettingReference.class, MacroImplementation.Scope.Static);
         defaultMacros.put(PwmAppName.class, MacroImplementation.Scope.Static);
         defaultMacros.put(PwmContextPath.class, MacroImplementation.Scope.System);
@@ -59,40 +55,6 @@ public abstract class InternalMacros {
         }
     }
 
-    public static class OtpSetupTimeMacro extends InternalAbstractMacro {
-        private static final Pattern PATTERN = Pattern.compile("@OtpSetupTime@");
-
-        public Pattern getRegExPattern() {
-            return PATTERN;
-        }
-
-        public String replaceValue(final String matchValue, final MacroRequestInfo macroRequestInfo)
-        {
-            final UserInfoBean userInfoBean = macroRequestInfo.getUserInfoBean();
-            if (userInfoBean != null && userInfoBean.getOtpUserRecord() != null && userInfoBean.getOtpUserRecord().getTimestamp() != null) {
-                return PwmConstants.DEFAULT_DATETIME_FORMAT.format(userInfoBean.getOtpUserRecord().getTimestamp());
-            }
-            return "";
-        }
-    }
-
-    public static class ResponseSetupTimeMacro extends InternalAbstractMacro {
-        private static final Pattern PATTERN = Pattern.compile("@ResponseSetupTime@");
-
-        public Pattern getRegExPattern() {
-            return PATTERN;
-        }
-
-        public String replaceValue(final String matchValue, final MacroRequestInfo macroRequestInfo)
-        {
-            final UserInfoBean userInfoBean = macroRequestInfo.getUserInfoBean();
-            if (userInfoBean != null && userInfoBean.getResponseInfoBean() != null && userInfoBean.getResponseInfoBean().getTimestamp() != null) {
-                return JavaHelper.toIsoDate(userInfoBean.getResponseInfoBean().getTimestamp());
-            }
-            return "";
-        }
-    }
-
     public static class PwmSettingReference extends InternalAbstractMacro {
         private static final Pattern PATTERN = Pattern.compile("@PwmSettingReference" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@" );
 

+ 37 - 1
src/main/java/password/pwm/util/macro/StandardMacros.java

@@ -83,7 +83,9 @@ public abstract class StandardMacros {
         defaultMacros.put(UserEmailMacro.class, MacroImplementation.Scope.User);
         defaultMacros.put(UserPasswordMacro.class, MacroImplementation.Scope.User);
         defaultMacros.put(UserLdapProfileMacro.class, MacroImplementation.Scope.User);
-        
+        defaultMacros.put(OtpSetupTimeMacro.class, MacroImplementation.Scope.User);
+        defaultMacros.put(ResponseSetupTimeMacro.class, MacroImplementation.Scope.User);
+
         // wrapper macros: must be at the end to allow Macro in Macro during parsing
         defaultMacros.put(EncodingMacro.class, MacroImplementation.Scope.System);
         STANDARD_MACROS = Collections.unmodifiableMap(defaultMacros);
@@ -657,4 +659,38 @@ public abstract class StandardMacros {
             return encodeType.encode(value);
         }
     }
+
+    public static class OtpSetupTimeMacro extends InternalMacros.InternalAbstractMacro {
+        private static final Pattern PATTERN = Pattern.compile("@OtpSetupTime@");
+
+        public Pattern getRegExPattern() {
+            return PATTERN;
+        }
+
+        public String replaceValue(final String matchValue, final MacroRequestInfo macroRequestInfo)
+        {
+            final UserInfoBean userInfoBean = macroRequestInfo.getUserInfoBean();
+            if (userInfoBean != null && userInfoBean.getOtpUserRecord() != null && userInfoBean.getOtpUserRecord().getTimestamp() != null) {
+                return PwmConstants.DEFAULT_DATETIME_FORMAT.format(userInfoBean.getOtpUserRecord().getTimestamp());
+            }
+            return "";
+        }
+    }
+
+    public static class ResponseSetupTimeMacro extends InternalMacros.InternalAbstractMacro {
+        private static final Pattern PATTERN = Pattern.compile("@ResponseSetupTime@");
+
+        public Pattern getRegExPattern() {
+            return PATTERN;
+        }
+
+        public String replaceValue(final String matchValue, final MacroRequestInfo macroRequestInfo)
+        {
+            final UserInfoBean userInfoBean = macroRequestInfo.getUserInfoBean();
+            if (userInfoBean != null && userInfoBean.getResponseInfoBean() != null && userInfoBean.getResponseInfoBean().getTimestamp() != null) {
+                return PwmConstants.DEFAULT_DATETIME_FORMAT.format(userInfoBean.getResponseInfoBean().getTimestamp());
+            }
+            return "";
+        }
+    }
 }

+ 2 - 0
src/main/resources/password/pwm/AppProperty.properties

@@ -239,6 +239,8 @@ smtp.subjectEncodingCharset=UTF8
 token.removalDelayMS=86400000
 token.purgeBatchSize=1000
 token.maxUniqueCreateAttempts=100
+token.resend.enabled=true
+token.resend.delayMS=3000
 urlshortener.url.regex=(https?://([^:@]+(:[^@]+)?@)?([a-zA-Z0-9.]+|d{1,3}.d{1,3}.d{1,3}.d{1,3}|[[0-9a-fA-F:]+])(:d{1,5})?/*[a-zA-Z0-9/\%_.]*?*[a-zA-Z0-9/\%_.=&#]*)
 wordlist.builtin.path=/WEB-INF/wordlist.zip
 ws.restClient.pwRule.haltOnError=true

+ 9 - 1
src/main/resources/password/pwm/config/PwmSetting.xml

@@ -198,6 +198,11 @@
             <value>true</value>
         </default>
     </setting>
+    <setting hidden="false" key="display.maskTokenFields" level="2" required="true">
+        <default>
+            <value>false</value>
+        </default>
+    </setting>
     <setting hidden="false" key="display.showCancelButton" level="1" required="true">
         <default>
             <value>true</value>
@@ -1485,6 +1490,9 @@
         <options>
             <option value="LOCAL">Local</option>
             <option value="CRYPTCOOKIE">Encrypted Cookie</option>
+            <!--
+            <option value="CRYPTREQUEST">Encrypted Request Parameter</option>
+            -->
         </options>
     </setting>
     <setting hidden="false" key="security.preventFraming" level="2">
@@ -1501,7 +1509,7 @@
     <setting hidden="false" key="security.cspHeader" level="2">
         <default>
             <!--<value><![CDATA[]]></value>-->
-            <value><![CDATA[default-src 'self'; object-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'self' 'unsafe-eval' 'unsafe-inline' 'nonce-%NONCE%' ; frame-src https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; report-uri /sspr/public/command/csp-report]]></value>
+            <value><![CDATA[default-src 'self'; object-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'self' 'unsafe-eval' 'unsafe-inline' 'nonce-%NONCE%' ; frame-src https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; report-uri /sspr/public/command/cspReport]]></value>
             <!-- 'unsafe-inline' on script-src is included for backward compatibility of CSP Level1 browsers.  CSP2 and future ignore it when the nonce is specified -->
         </default>
     </setting>

+ 2 - 2
src/main/resources/password/pwm/i18n/ConfigGuide.properties

@@ -25,9 +25,9 @@ ldap_admin_title=LDAP Proxy Credentials
 ldap_admin_title_proxy-dn=Proxy DN
 ldap_admin_title_proxy-pw=Proxy Password
 ldap_cert_description=The following are the LDAP server certificates read from the server at <code>%1%</code>. Please verify these certificates match your LDAP server.
-ldap_context_description=Please enter the top level container of your LDAP directory that contains users. This sets the top level LDAP container where an LDAP sub-tree search is performed to find user entries. If you need to enter multiple containers, you can add them after this guide completes.
+ldap_context_description=Please enter the top level container of your LDAP directory that contains users. This sets the top level LDAP container where an LDAP sub-tree search is performed to find user entries. If you need to enter multiple containers, you can add them after this guide completes.  Authentication to @PwmAppName@ is permitted only for users that are contained within the configured context values.
 ldap_context_admin_title=Administrators Group
-ldap_context_admin_description=<p>A group in your LDAP directory will be used to control administrative access to @PwmAppName@.</p><p>Please enter the LDAP distinguished name (DN) of the group to use to control administrative access.  Each member of this group will have administrative access to this application.</p>
+ldap_context_admin_description=<p>A group in your LDAP directory will be used to control administrative access to @PwmAppName@.</p><p>Please enter the LDAP distinguished name (DN) of the group to use to control administrative access.  Each member of this group will have administrative access to this application.</p><p>As with other users, administrative users must be contained within the previously configured LDAP Login Root Context.
 ldap_server_description=Enter the connection information for your LDAP server.  After the configuration guide is completed you can enter additional servers.  Enter the real address of your LDAP server; do not use a virtual address or proxy server address.
 ldap_server_title=LDAP Server
 ldap_server_title_hostname=Hostname

+ 3 - 1
src/main/resources/password/pwm/i18n/Display.properties

@@ -55,6 +55,7 @@ Button_Show=Show
 Button_Show_Responses=Show Responses
 Button_Skip=Skip
 Button_SMS=SMS
+Button_TokenResend=Resend Code
 Button_Unlock=Unlock
 Button_UnlockPassword=Unlock Password
 Button_Update=Update
@@ -165,6 +166,7 @@ Display_SetupOtp_iPhone_Steps=<b>On your iPhone, tap the App Store icon.</b><ol>
 Display_SetupOtp_Other_Title=Other
 Display_SetupOtp_Other_Steps=<b>Find a compatible two-factor app.</b><ul><li>Try searching your device's app store for <b>Google Authenticator</b>.<br/>Many devices have compatible apps.</li><li>Try looking for an app that supports "<b>TOTP security tokens"</b> or "RFC6238"</li><li>Download and install the application.</li></ul><b>Next, open and configure the app.</b><ol><li>Enter the data below or scan the code as the app instructs.</li><li>Once you have configured the app, click the continue button.</li></ol>
 Display_TokenDestination=Token Destination
+Display_TokenResend=Your security code should arrive right away.  If you have waited for a while and haven't yet received a code, click the resend code button to receive a new code.
 Display_UsernameHeader=@User:ID@
 Display_UsernameFooter=@User:ID@
 Display_WarnExistingOtpSecretTime=You have already enrolled your device on <span class="timestamp">%1%</span>.  You can test your current device by typing in the generated code below.  If you continue, you can re-configure your current device.
@@ -238,7 +240,7 @@ Field_VerificationMethodChallengeResponses=Secret Questions and Answers
 Field_VerificationMethodAttributes=Personal Data
 Field_VerificationMethodRemoteResponses=External Responses
 Field_VerificationMethodNAAF=Advanced Authentication
-Field_VerificationMethodOAuth=External Authentication
+Field_VerificationMethodOAuth=External OAuth Authentication
 Description_VerificationMethodPreviousAuth=
 Description_VerificationMethodToken=
 Description_VerificationMethodOTP=

+ 1 - 0
src/main/resources/password/pwm/i18n/Message.properties

@@ -169,3 +169,4 @@ Success_UpdateProfile=Your user information has been successfully updated.
 Success_UpdateForm=Your profile is ready to be updated.  Continue when ready.
 Success_Action=The action %1% has completed successfully.
 Success_OtpSetup=Your device enrollment has been successfully completed.
+Success_TokenResend=A new security code has been sent to you.

+ 3 - 1
src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -285,6 +285,7 @@ Setting_Description_display.js.custom=Specify a custom JavaScript that @PwmAppNa
 Setting_Description_display.logoutButton=Enable this option to show a logout button in the header and other menus as appropriate to authenticated users and administrators.
 Setting_Description_display.maskPasswordFields=Enable this option to mask sensitive input fields with standard "password" masking.  If set to false, @PwmAppName@ displays sensitive fields as normal text input fields.
 Setting_Description_display.maskResponseFields=Enable this option to mask Challenge/Response answer input fields with standard "password" masking.  If set to false, @PwmAppName@ displays response fields as normal text input fields.  This setting applies to both setup responses and forgotten password response entry screens.
+Setting_Description_display.maskTokenFields=Enable this option to mask token input fields with standard "password" masking.  When enabled, multi-line tokens (such as crypto-format tokens) will not be easily input by users.
 Setting_Description_display.newuser.agreement=<p>Specify a message to display to users before allowing them to register as a new user. If blank, @PwmAppName@ does not display the new user agreement page to the user. This message can include HTML tags.
 Setting_Description_display.password.changeAgreement=<p>Specify a message to display to users before allowing them to change their passwords. If blank, @PwmAppName@ does not display the change password agreement page to the users. This message can include HTML tags.</p> <p>This setting can use macros.  For more information about macros, see the "View" menu "Show Macro Help".</p>
 Setting_Description_display.password.completeMessage=<p>Specify a message to display to users when they complete a password change.  If blank,  @PwmAppName@ does not display the change password completion page to the user. This message can include HTML tags.</p> <p>This setting can use macros.  For more information, see the "View" menu "Show Macro Help".</p>
@@ -442,7 +443,7 @@ Setting_Description_ldap.profile.enabled=Enable this option to indicate if this
 Setting_Description_ldap.profile.list=List of LDAP profiles.  Each LDAP profile defines a unique LDAP data environment.  Depending on the directory type and configuration, this could mean a unique domain, forest, tree, or possibly server.  Each profile can have multiple redundant servers defined, however, those servers should share the same data.<br/><br/>Be careful when renaming or removing values in this list, if you remove or rename a profile name @PwmAppName@ removes the associated settings.<br/><br/>During authentication, @PwmAppName@ searches each profile in the order listed here.
 Setting_Description_ldap.proxy.password=Specify the password of the LDAP Proxy User.
 Setting_Description_ldap.proxy.username=Specify the LDAP Proxy User @PwmAppName@ uses to access the LDAP directory. This user must have rights to browse users, and manage password attributes on the user object.<br/><br/>This value must be in LDAP distinguished name format, even if your LDAP directory accepts other types of values for the bind DN.  An example of this format is <i>cn\=admin,o\=example</i> or <i>cn\=administrator,cn\=users,dc\=subdomain,dc\=domain,dc\=net</i>.<br/><br/>Generally, the proxy user needs read/browse object rights to all user objects it manages, as well as create object rights in the new user container (if enabled).
-Setting_Description_ldap.rootContexts=Specify the base context to search for user names during authentication and other operations.  @PwmAppName@ searches each context in order until it finds a single match.  If it finds multiple matches in a single context, @PwmAppName@ considers it a match not found.  @PwmAppName@ executes each search serially so avoid large numbers of contexts to improve search performance.
+Setting_Description_ldap.rootContexts=Specify the base context(s) to search for user names during authentication and other operations.  During authentication, @PwmAppName@ will perform a subtree search in each context listed.  In cases where more than a single user is found during a search, the process configured in setting <code>@PwmSettingReference\:ldap.duplicateMode@</code> is used to handle the duplicates.<br/><br/>Authentication to @PwmAppName@ is permitted only for users that are contained within the configured context values.
 Setting_Description_ldap.search.timeoutSeconds=Specify the maximum amount of seconds to wait for an LDAP search to complete.
 Setting_Description_ldap.selectableContextMode=Control if the ldap context or profile is shown to the user as an option during user identification (login, forgotten password, etc).
 Setting_Description_ldap.selectableContexts=(Optional) Add another field to the form-based login screen and other user search screens.  The field allows the user to select a specific context. This is for situations where the LDAP directory does not have unique user names throughout the entire directory. <br/><br/>Values can further be set with both a display value and a context, separated by three colons.<br/><br/>For example\:<p><b>ou\=sf,ou\=ca,o\=example\:\:\:San Francisco</b><br/<b>ou\=lon,ou\=uk,o\=example\:\:\:London</b><br/<b>ou\=nyc,ou\=ny,o\=example\:\:\:New York</b><br/</p>
@@ -754,6 +755,7 @@ Setting_Label_display.js.custom=Embedded JavaScript
 Setting_Label_display.logoutButton=Show Logout Button
 Setting_Label_display.maskPasswordFields=Mask Password Fields
 Setting_Label_display.maskResponseFields=Mask Response Fields
+Setting_Label_display.maskTokenFields=Mask Token Input Fields
 Setting_Label_display.newuser.agreement=New User Agreement Message
 Setting_Label_display.password.changeAgreement=Password Change Agreement Message
 Setting_Label_display.password.completeMessage=Password Change Completion Message

+ 3 - 3
src/main/webapp/WEB-INF/jsp/accountinformation.jsp

@@ -27,19 +27,19 @@
 <%@ page import="password.pwm.config.PwmSetting" %>
 <%@ page import="password.pwm.config.option.ViewStatusFields" %>
 <%@ page import="password.pwm.http.JspUtility" %>
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%@ page import="password.pwm.i18n.Display" %>
 <%@ page import="password.pwm.svc.event.UserAuditRecord" %>
 <%@ page import="password.pwm.util.LocaleHelper" %>
+<%@ page import="password.pwm.util.java.JavaHelper" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="password.pwm.util.java.TimeDuration" %>
-<%@ page import="java.text.DateFormat" %>
 <%@ page import="java.util.Collections" %>
 <%@ page import="java.util.List" %>
 <%@ page import="java.util.Locale" %>
 <%@ page import="java.util.Map" %>
 <%@ page import="java.util.Set" %>
-<%@ page import="password.pwm.util.java.JavaHelper" %>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
@@ -403,7 +403,7 @@
 </div>
 <div class="buttonbar">
     <form action="<pwm:url url='<%=PwmServletDefinition.Command.servletUrl()%>' addContext="true"/>" method="post" enctype="application/x-www-form-urlencoded">
-        <input type="hidden" name="processAction" value="continue"/>
+        <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
         <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
         <button type="submit" name="button" class="btn" id="button_continue">
             <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>

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

@@ -23,6 +23,7 @@
 <%@ page import="password.pwm.Permission" %>
 <%@ page import="password.pwm.http.JspUtility" %>
 <%@ page import="password.pwm.http.PwmSession" %>
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%@ page import="password.pwm.util.java.JavaHelper" %>
 <% final PwmRequest debug_pwmRequest = JspUtility.getPwmRequest(pageContext); %>
@@ -77,9 +78,7 @@
             <form action="<pwm:url url='<%=PwmServletDefinition.Command.servletUrl()%>' addContext="true"/>" method="post" enctype="application/x-www-form-urlencoded">
                 <input tabindex="2" type="submit" name="continue_btn" class="btn"
                        value="    <pwm:display key="Button_Continue"/>    "/>
-                <input type="hidden"
-                       name="processAction"
-                       value="continue"/>
+                <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
             </form>
         </div>
     </div>

+ 6 - 6
src/main/webapp/WEB-INF/jsp/changepassword-complete.jsp

@@ -1,5 +1,5 @@
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -40,11 +40,11 @@
         <div id="agreementText" class="agreementText"><%= expandedText %></div>
         <div class="buttonbar">
             <form action="<pwm:url url='<%=PwmServletDefinition.Command.servletUrl()%>' addContext="true"/>" method="post" enctype="application/x-www-form-urlencoded" class="pwm-form">
-                    <input type="hidden" name="processAction" value="continue"/>
-                    <button type="submit" name="button" class="btn" id="submitBtn">
-                        <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
-                        <pwm:display key="Button_Continue"/>
-                    </button>
+                <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
+                <button type="submit" name="button" class="btn" id="submitBtn">
+                    <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
+                    <pwm:display key="Button_Continue"/>
+                </button>
                 <input type="hidden" name="pwmFormID" id="pwmFormID" value="<pwm:FormID/>"/>
             </form>
             <br/>

+ 2 - 4
src/main/webapp/WEB-INF/jsp/error-http.jsp

@@ -1,7 +1,7 @@
 <%@ page import="password.pwm.error.PwmError" %>
 <%@ page import="password.pwm.http.JspUtility" %>
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
-<%@ page import="password.pwm.error.PwmException" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -60,9 +60,7 @@
         <br/>
         <div class="buttonbar">
             <form action="<pwm:url url='<%=PwmServletDefinition.Command.servletUrl()%>' addContext="true"/>" method="post" enctype="application/x-www-form-urlencoded">
-                <input type="hidden"
-                       name="processAction"
-                       value="continue"/>
+                <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
                 <input type="submit" name="button" class="btn"
                        value="    <pwm:display key="Button_Continue"/>    "
                        id="button_continue"/>

+ 2 - 2
src/main/webapp/WEB-INF/jsp/error.jsp

@@ -1,7 +1,7 @@
 <%@ page import="password.pwm.error.ErrorInformation" %>
 <%@ page import="password.pwm.http.JspUtility" %>
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -53,7 +53,7 @@
             <% if (errorInformation != null && !errorInformation.getError().isErrorIsPermanent()) { %>
             <div class="buttonbar">
                 <form action="<pwm:url url='<%=PwmServletDefinition.Command.servletUrl()%>' addContext="true"/>" method="post" enctype="application/x-www-form-urlencoded">
-                    <input type="hidden" name="processAction" value="continue"/>
+                    <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
                     <button type="submit" name="button" class="btn" id="button_continue" autofocus="autofocus">
                         <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
                         <pwm:display key="Button_Continue"/>

+ 41 - 2
src/main/webapp/WEB-INF/jsp/forgottenpassword-entertoken.jsp

@@ -25,6 +25,7 @@
 <%@ page import="password.pwm.http.bean.ForgottenPasswordBean" %>
 <%@ page import="password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet"%>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
+<%  final boolean resendEnabled = Boolean.parseBoolean(JspUtility.getPwmRequest(pageContext).getConfig().readAppProperty(AppProperty.TOKEN_RESEND_ENABLED)); %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ include file="fragment/header.jsp" %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
@@ -38,10 +39,48 @@
         <% final ForgottenPasswordBean fpb = JspUtility.getSessionBean(pageContext, ForgottenPasswordBean.class); %>
         <% final String destination = fpb.getProgress().getTokenSentAddress(); %>
         <p><pwm:display key="Display_RecoverEnterCode" value1="<%=destination%>"/></p>
+        <% if (resendEnabled) { %>
+        <p><pwm:display key="Display_TokenResend"/></p>
+        <p>
+            <button type="button" id="button-resend-token" class="btn">
+                <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-refresh"></span></pwm:if>
+                <pwm:display key="Button_TokenResend"/>
+            </button>
+        </p>
+        <br/>
+        <pwm:script>
+            <script type="text/javascript">
+                PWM_GLOBAL['startupFunctions'].push(function() {
+                    PWM_MAIN.addEventHandler('button-resend-token','click',function(){
+                        PWM_MAIN.showWaitDialog({loadFunction:function(){
+                            var loadFunction = function(data){
+                                if (data['error']) {
+                                    PWM_MAIN.showErrorDialog(data);
+                                } else {
+                                    var resultText = data['successMessage'];
+                                    PWM_MAIN.showDialog({
+                                        title: PWM_MAIN.showString('Title_Success'),
+                                        text: resultText,
+                                        okAction: function () {
+                                            var inputField = PWM_MAIN.getObject('<%=PwmConstants.PARAM_TOKEN%>');
+                                            if (inputField) {
+                                                inputField.value = '';
+                                                inputField.focus();
+                                            }
+                                        }
+                                    });
+                                }
+                            };
+                            PWM_MAIN.ajaxRequest('forgottenpassword?processAction=resendToken',loadFunction);
+                        }});
+                    })
+                });
+            </script>
+        </pwm:script>
+        <% } %>
         <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form" autocomplete="off">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
-            <h2><label for="<%=PwmConstants.PARAM_TOKEN%>"><pwm:display key="Field_Code"/></label></h2>
-            <textarea id="<%=PwmConstants.PARAM_TOKEN%>" name="<%=PwmConstants.PARAM_TOKEN%>" class="tokenInput" required="required" <pwm:autofocus/> ></textarea>
+            <%@ include file="/WEB-INF/jsp/fragment/token-form-field.jsp" %>
             <div class="buttonbar">
                 <button type="submit" class="btn" name="search" id="submitBtn">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-check"></span></pwm:if>

+ 2 - 3
src/main/webapp/WEB-INF/jsp/forgottenpassword-method.jsp

@@ -1,7 +1,6 @@
 <%@ page import="password.pwm.config.option.IdentityVerificationMethod" %>
-<%@ page import="java.util.HashSet" %>
+<%@ page import="java.util.LinkedHashSet" %>
 <%@ page import="java.util.Set" %>
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -29,7 +28,7 @@
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%
     final PwmRequest pwmRequest = PwmRequest.forRequest(request, response);
-    final Set<IdentityVerificationMethod> methods = new HashSet<IdentityVerificationMethod>((Set<IdentityVerificationMethod>) JspUtility.getAttribute(pageContext, PwmRequest.Attribute.AvailableAuthMethods));
+    final Set<IdentityVerificationMethod> methods = new LinkedHashSet<IdentityVerificationMethod>((Set<IdentityVerificationMethod>) JspUtility.getAttribute(pageContext, PwmRequest.Attribute.AvailableAuthMethods));
 %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>

+ 2 - 2
src/main/webapp/WEB-INF/jsp/forgottenpassword-tokenchoice.jsp

@@ -38,7 +38,7 @@
         <table class="noborder">
             <tr>
                 <td style="text-align: center">
-                    <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search">
+                    <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form">
                         <button class="btn" type="submit" name="submitBtn">
                             <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-file-text"></span></pwm:if>
                             <pwm:display key="Button_Email"/>
@@ -59,7 +59,7 @@
             </tr>
             <tr>
                 <td style="text-align: center">
-                    <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search">
+                    <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form">
                         <button class="btn" type="submit" name="submitBtn">
                             <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-phone"></span></pwm:if>
                             <pwm:display key="Button_SMS"/>

+ 2 - 2
src/main/webapp/WEB-INF/jsp/forgottenusername-complete.jsp

@@ -1,5 +1,5 @@
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -40,7 +40,7 @@
         <div id="agreementText" class="agreementText"><%= expandedText %></div>
         <div class="buttonbar">
             <form action="<pwm:url url='<%=PwmServletDefinition.Command.servletUrl()%>' addContext="true"/>" method="post" enctype="application/x-www-form-urlencoded" class="pwm-form">
-                <input type="hidden" name="processAction" value="continue"/>
+                <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
                 <button type="submit" name="button" class="btn" id="submitBtn">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
                     <pwm:display key="Button_Continue"/>

+ 7 - 6
src/main/webapp/WEB-INF/jsp/fragment/cancel-form.jsp

@@ -1,5 +1,6 @@
-<%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%@ page import="password.pwm.PwmConstants" %>
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
+<%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 
 <%--
@@ -27,9 +28,9 @@
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <pwm:if test="<%=PwmIfTest.showCancel%>">
-<pwm:if test="<%=PwmIfTest.forcedPageView%>" negate="true">
-  <form id="form-hidden-cancel" action="<pwm:url addContext="true" url='<%=PwmServletDefinition.Command.servletUrl()%>'/>" method="get">
-    <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="continue"/>
-  </form>
-</pwm:if>
+  <pwm:if test="<%=PwmIfTest.forcedPageView%>" negate="true">
+    <form id="form-hidden-cancel" action="<pwm:url addContext="true" url='<%=PwmServletDefinition.Command.servletUrl()%>'/>" method="get">
+      <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
+    </form>
+  </pwm:if>
 </pwm:if>

+ 1 - 1
src/main/webapp/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp

@@ -34,7 +34,7 @@
         <script type="text/javascript">
             PWM_GLOBAL['startupFunctions'].push(function(){
                 PWM_MAIN.addEventHandler('button-sendReset', 'click',function() {
-                    PWM_MAIN.submitPostAction('<%=PwmServletDefinition.ForgottenPassword.servletUrlName()%>', '<%=ForgottenPasswordServlet.ForgottenPasswordAction.reset%>');
+                    PWM_MAIN.submitPostAction('<pwm:current-url/>', '<%=ForgottenPasswordServlet.ForgottenPasswordAction.reset%>');
                 });
             });
         </script>

+ 56 - 0
src/main/webapp/WEB-INF/jsp/fragment/token-form-field.jsp

@@ -0,0 +1,56 @@
+<%--
+  ~ 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
+  --%>
+
+<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
+<%@ page import="password.pwm.PwmConstants" %>
+
+<%--
+  ~ 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
+  --%>
+
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
+<%@ taglib uri="pwm" prefix="pwm" %>
+<h2><label for="<%=PwmConstants.PARAM_TOKEN%>"><pwm:display key="Field_Code"/></label></h2>
+<pwm:if test="<%=PwmIfTest.maskTokenInput%>">
+    <input type="password" class="inputfield passwordfield" id="<%=PwmConstants.PARAM_TOKEN%>" name="<%=PwmConstants.PARAM_TOKEN%>" required="required" <pwm:autofocus/> ></textarea>
+</pwm:if>
+<pwm:if test="<%=PwmIfTest.maskTokenInput%>" negate="true">
+    <textarea id="<%=PwmConstants.PARAM_TOKEN%>" name="<%=PwmConstants.PARAM_TOKEN%>" class="tokenInput" required="required" <pwm:autofocus/> ></textarea>
+</pwm:if>

+ 1 - 2
src/main/webapp/WEB-INF/jsp/newuser-entercode.jsp

@@ -48,8 +48,7 @@
         <% } %>
         <form action="<pwm:current-url/>" method="post" autocomplete="off" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form">
             <%@ include file="fragment/message.jsp" %>
-            <h2><label for="<%=PwmConstants.PARAM_TOKEN%>"><pwm:display key="Field_Code"/></label></h2>
-            <textarea id="<%=PwmConstants.PARAM_TOKEN%>" name="<%=PwmConstants.PARAM_TOKEN%>" <pwm:autofocus/> class="tokenInput"></textarea>
+            <%@ include file="/WEB-INF/jsp/fragment/token-form-field.jsp"%>
             <div class="buttonbar">
                 <button type="submit" class="btn" name="search" id="submitBtn">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-check"></span></pwm:if>

+ 2 - 1
src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp

@@ -3,6 +3,7 @@
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="java.util.Map" %>
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -75,7 +76,7 @@
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-times"></span></pwm:if>
                     <pwm:display key="Button_Cancel"/>
                 </button>
-                <input type="hidden" name="processAction" value="continue"/>
+                <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
                 <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
             </form>
             <% } %>

+ 2 - 1
src/main/webapp/WEB-INF/jsp/success.jsp

@@ -1,5 +1,6 @@
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
+<%@ page import="password.pwm.http.servlet.CommandServlet" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -39,7 +40,7 @@
               enctype="application/x-www-form-urlencoded" class="pwm-form">
             <p><pwm:SuccessMessage/></p>
             <div class="buttonbar">
-                <input type="hidden" name="processAction" value="continue"/>
+                <input type="hidden" name="<%=PwmConstants.PARAM_ACTION_REQUEST%>" value="<%=CommandServlet.CommandAction.next.toString()%>"/>
                 <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
                 <button type="submit" name="button" class="btn" id="submitBtn" autofocus>
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>

+ 1 - 2
src/main/webapp/WEB-INF/jsp/updateprofile-entercode.jsp

@@ -50,8 +50,7 @@
         <% } %>
         <form action="<pwm:current-url/>" method="post" autocomplete="off" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form">
             <%@ include file="fragment/message.jsp" %>
-            <h2><label for="<%=PwmConstants.PARAM_TOKEN%>"><pwm:display key="Field_Code"/></label></h2>
-            <textarea id="<%=PwmConstants.PARAM_TOKEN%>" name="<%=PwmConstants.PARAM_TOKEN%>" <pwm:autofocus/> class="tokenInput"></textarea>
+            <%@ include file="/WEB-INF/jsp/fragment/token-form-field.jsp"%>
             <div class="buttonbar">
                 <button type="submit" class="btn" name="search" id="submitBtn">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-check"></span></pwm:if>

+ 5 - 5
src/main/webapp/public/index.jsp

@@ -49,7 +49,7 @@
         <a id="Button_ForgottenPassword" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.ForgottenPassword.servletUrl()%>'/>">
             <div class="tile">
                 <div class="tile-content">
-                    <div class="tile-image pwm-icon-unlock"></div>
+                    <div class="tile-image forgotten-image"></div>
                     <div class="tile-title" title="<pwm:display key='Title_ForgottenPassword'/>"><pwm:display key="Title_ForgottenPassword"/></div>
                     <div class="tile-subtitle" title="<pwm:display key='Long_Title_ForgottenPassword'/>"><pwm:display key="Long_Title_ForgottenPassword"/></div>
                 </div>
@@ -60,7 +60,7 @@
         <a id="Button_ForgottenUsername" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.ForgottenUsername.servletUrl()%>'/>">
             <div class="tile">
                 <div class="tile-content">
-                    <div class="tile-image pwm-icon-unlock"></div>
+                    <div class="tile-image forgotten-image"></div>
                     <div class="tile-title" title="<pwm:display key='Title_ForgottenUsername'/>"><pwm:display key="Title_ForgottenUsername"/></div>
                     <div class="tile-subtitle" title="<pwm:display key='Long_Title_ForgottenUsername'/>"><pwm:display key="Long_Title_ForgottenUsername"/></div>
                 </div>
@@ -71,7 +71,7 @@
         <a id="Button_ActivateUser" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.ActivateUser.servletUrl()%>'/>">
             <div class="tile">
                 <div class="tile-content">
-                    <div class="tile-image pwm-icon-graduation-cap"></div>
+                    <div class="tile-image activation-image"></div>
                     <div class="tile-title" title="<pwm:display key='Title_ActivateUser'/>"><pwm:display key="Title_ActivateUser"/></div>
                     <div class="tile-subtitle" title="<pwm:display key='Long_Title_ActivateUser'/>"><pwm:display key="Long_Title_ActivateUser"/></div>
                 </div>
@@ -82,7 +82,7 @@
         <a id="Button_NewUser" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.NewUser.servletUrl()%>'/>">
             <div class="tile">
                 <div class="tile-content">
-                    <div class="tile-image pwm-icon-file-text-o"></div>
+                    <div class="tile-image newuser-image"></div>
                     <div class="tile-title" title="<pwm:display key='Title_NewUser'/>"><pwm:display key="Title_NewUser"/></div>
                     <div class="tile-subtitle" title="<pwm:display key='Long_Title_NewUser'/>"><pwm:display key="Long_Title_NewUser"/></div>
                 </div>
@@ -93,7 +93,7 @@
         <a id="Button_PeopleSearch" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.PublicPeopleSearch.servletUrl()%>'/>">
             <div class="tile">
                 <div class="tile-content">
-                    <div class="tile-image pwm-icon-search"></div>
+                    <div class="tile-image search-image"></div>
                     <div class="tile-title" title="<pwm:display key='Title_PeopleSearch'/>"><pwm:display key="Title_PeopleSearch"/></div>
                     <div class="tile-subtitle" title="<pwm:display key='Long_Title_PeopleSearch'/>"><pwm:display key="Long_Title_PeopleSearch"/></div>
                 </div>

+ 1 - 1
src/main/webapp/public/resources/js/main.js

@@ -637,7 +637,7 @@ PWM_MAIN.showLocaleSelectionMenu = function(nextFunction, options) {
             if (array.indexOf(excludeLocales, localeKey) == -1) {
                 var loopDisplayName = localeData[localeKey];
                 var flagCode = PWM_GLOBAL['localeFlags'][localeKey];
-                var flagUrl = PWM_GLOBAL['url-resources'] + '/flags/png/' + flagCode + '.png';
+                var flagUrl = PWM_GLOBAL['url-resources'] + '/webjars/famfamfam-flags/dist/png/' + flagCode + '.png';
                 bodyHtml += '<tr style="cursor:pointer" id="locale-row-' + localeKey + '">';
                 bodyHtml += '<td><img src="' + flagUrl + '"/></td>';
                 bodyHtml += '<td>' + loopDisplayName + '</td>';

+ 12 - 0
src/main/webapp/public/resources/style.css

@@ -337,6 +337,18 @@ input[type=password]::-ms-reveal{display: none;}
     content: "\f002";
 }
 
+.tile-image.newuser-image:before {
+    content: "\f234";
+}
+
+.tile-image.activation-image:before {
+    content: "\f19d";
+}
+
+.tile-image.forgotten-image:before {
+    content: "\f09c";
+}
+
 .tile-image.security-image:before {
     content: "\f0cb";
 }