瀏覽代碼

helpdesk verification enhancements

Jason Rivard 9 年之前
父節點
當前提交
9ff7db2a43
共有 42 個文件被更改,包括 655 次插入291 次删除
  1. 2 0
      src/main/java/password/pwm/AppProperty.java
  2. 6 6
      src/main/java/password/pwm/VerificationMethodSystem.java
  3. 7 7
      src/main/java/password/pwm/bean/RemoteVerificationResponseBean.java
  4. 2 2
      src/main/java/password/pwm/config/PwmSetting.java
  5. 6 6
      src/main/java/password/pwm/config/option/IdentityVerificationMethod.java
  6. 4 4
      src/main/java/password/pwm/config/profile/AbstractProfile.java
  7. 6 6
      src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java
  8. 5 5
      src/main/java/password/pwm/config/profile/HelpdeskProfile.java
  9. 11 46
      src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java
  10. 31 9
      src/main/java/password/pwm/config/value/VerificationMethodValue.java
  11. 1 0
      src/main/java/password/pwm/http/PwmRequest.java
  12. 19 19
      src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java
  13. 2 0
      src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java
  14. 43 43
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  15. 2 2
      src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java
  16. 11 0
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientDataBean.java
  17. 141 17
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  18. 10 1
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequestBean.java
  19. 3 3
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationResponseBean.java
  20. 140 9
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationStateBean.java
  21. 1 1
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  22. 2 0
      src/main/java/password/pwm/i18n/Display.java
  23. 2 0
      src/main/java/password/pwm/i18n/Message.java
  24. 10 8
      src/main/java/password/pwm/ldap/UserSearchEngine.java
  25. 2 0
      src/main/java/password/pwm/svc/event/AuditEvent.java
  26. 8 10
      src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java
  27. 5 5
      src/main/java/password/pwm/ws/client/rest/naaf/NAAFLoginSequence.java
  28. 2 2
      src/main/java/password/pwm/ws/client/rest/naaf/PwmNAAFVerificationMethod.java
  29. 3 1
      src/main/resources/password/pwm/AppProperty.properties
  30. 11 10
      src/main/resources/password/pwm/config/PwmSetting.xml
  31. 2 0
      src/main/resources/password/pwm/i18n/Display.properties
  32. 2 0
      src/main/resources/password/pwm/i18n/Message.properties
  33. 1 1
      src/main/webapp/WEB-INF/jsp/configeditor.jsp
  34. 3 3
      src/main/webapp/WEB-INF/jsp/forgottenpassword-method.jsp
  35. 3 3
      src/main/webapp/WEB-INF/jsp/forgottenpassword-naaf.jsp
  36. 3 3
      src/main/webapp/WEB-INF/jsp/forgottenpassword-remote.jsp
  37. 2 8
      src/main/webapp/WEB-INF/jsp/helpdesk-detail.jsp
  38. 7 1
      src/main/webapp/WEB-INF/jsp/helpdesk.jsp
  39. 1 1
      src/main/webapp/WEB-INF/jsp/peoplesearch.jsp
  40. 1 1
      src/main/webapp/public/resources/js/admin.js
  41. 1 1
      src/main/webapp/public/resources/js/configeditor.js
  42. 131 47
      src/main/webapp/public/resources/js/helpdesk.js

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

@@ -148,6 +148,8 @@ public enum AppProperty {
     HEALTH_JAVA_MIN_HEAP_BYTES                      ("health.java.minHeapBytes"),
     HEALTH_JAVA_MIN_HEAP_BYTES                      ("health.java.minHeapBytes"),
     HELPDESK_TOKEN_MAX_AGE                          ("helpdesk.token.maxAgeSeconds"),
     HELPDESK_TOKEN_MAX_AGE                          ("helpdesk.token.maxAgeSeconds"),
     HELPDESK_TOKEN_VALUE                            ("helpdesk.token.value"),
     HELPDESK_TOKEN_VALUE                            ("helpdesk.token.value"),
+    HELPDESK_VERIFICATION_INVALID_DELAY_MS          ("helpdesk.verification.invalid.delayMs"),
+    HELPDESK_VERIFICATION_TIMEOUT_SECONDS           ("helpdesk.verification.timeoutSeconds"),
     LDAP_CHAI_SETTINGS                              ("ldap.chaiSettings"),
     LDAP_CHAI_SETTINGS                              ("ldap.chaiSettings"),
     LDAP_CONNECTION_TIMEOUT                         ("ldap.connection.timeoutMS"),
     LDAP_CONNECTION_TIMEOUT                         ("ldap.connection.timeoutMS"),
     LDAP_PROFILE_RETRY_DELAY                        ("ldap.profile.retryDelayMS"),
     LDAP_PROFILE_RETRY_DELAY                        ("ldap.profile.retryDelayMS"),

+ 6 - 6
src/main/java/password/pwm/RecoveryVerificationMethod.java → src/main/java/password/pwm/VerificationMethodSystem.java

@@ -32,7 +32,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 
 
-public interface RecoveryVerificationMethod {
+public interface VerificationMethodSystem {
     enum VerificationState {
     enum VerificationState {
         INPROGRESS,
         INPROGRESS,
         FAILED,
         FAILED,
@@ -65,15 +65,15 @@ public interface RecoveryVerificationMethod {
         }
         }
     }
     }
 
 
-    public List<UserPrompt> getCurrentPrompts() throws PwmUnrecoverableException;
+    List<UserPrompt> getCurrentPrompts() throws PwmUnrecoverableException;
 
 
-    public String getCurrentDisplayInstructions();
+    String getCurrentDisplayInstructions();
 
 
-    public ErrorInformation respondToPrompts(final Map<String, String> answers) throws PwmUnrecoverableException;
+    ErrorInformation respondToPrompts(final Map<String, String> answers) throws PwmUnrecoverableException;
 
 
-    public VerificationState getVerificationState();
+    VerificationState getVerificationState();
 
 
-    public void init(final PwmApplication pwmApplication, final UserInfoBean userInfoBean, SessionLabel sessionLabel, Locale locale)
+    void init(final PwmApplication pwmApplication, final UserInfoBean userInfoBean, SessionLabel sessionLabel, Locale locale)
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
             ;
             ;
 
 

+ 7 - 7
src/main/java/password/pwm/bean/RemoteVerificationResponseBean.java

@@ -22,15 +22,15 @@
 
 
 package password.pwm.bean;
 package password.pwm.bean;
 
 
-import password.pwm.RecoveryVerificationMethod;
+import password.pwm.VerificationMethodSystem;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.List;
 import java.util.List;
 
 
 public class RemoteVerificationResponseBean implements Serializable {
 public class RemoteVerificationResponseBean implements Serializable {
     private String displayInstructions;
     private String displayInstructions;
-    private RecoveryVerificationMethod.VerificationState verificationState;
-    private List<RecoveryVerificationMethod.UserPromptBean> userPrompts;
+    private VerificationMethodSystem.VerificationState verificationState;
+    private List<VerificationMethodSystem.UserPromptBean> userPrompts;
     private String errorMessage;
     private String errorMessage;
 
 
     public String getDisplayInstructions() {
     public String getDisplayInstructions() {
@@ -41,19 +41,19 @@ public class RemoteVerificationResponseBean implements Serializable {
         this.displayInstructions = displayInstructions;
         this.displayInstructions = displayInstructions;
     }
     }
 
 
-    public RecoveryVerificationMethod.VerificationState getVerificationState() {
+    public VerificationMethodSystem.VerificationState getVerificationState() {
         return verificationState;
         return verificationState;
     }
     }
 
 
-    public void setVerificationState(RecoveryVerificationMethod.VerificationState verificationState) {
+    public void setVerificationState(VerificationMethodSystem.VerificationState verificationState) {
         this.verificationState = verificationState;
         this.verificationState = verificationState;
     }
     }
 
 
-    public List<RecoveryVerificationMethod.UserPromptBean> getUserPrompts() {
+    public List<VerificationMethodSystem.UserPromptBean> getUserPrompts() {
         return userPrompts;
         return userPrompts;
     }
     }
 
 
-    public void setUserPrompts(List<RecoveryVerificationMethod.UserPromptBean> userPrompts) {
+    public void setUserPrompts(List<VerificationMethodSystem.UserPromptBean> userPrompts) {
         this.userPrompts = userPrompts;
         this.userPrompts = userPrompts;
     }
     }
 
 

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

@@ -935,8 +935,6 @@ public enum PwmSetting {
             "helpdesk.displayName", PwmSettingSyntax.STRING, PwmSettingCategory.HELPDESK_PROFILE),
             "helpdesk.displayName", PwmSettingSyntax.STRING, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_TOKEN_SEND_METHOD(
     HELPDESK_TOKEN_SEND_METHOD(
             "helpdesk.token.sendMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.HELPDESK_PROFILE),
             "helpdesk.token.sendMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.HELPDESK_PROFILE),
-    HELPDESK_ENABLE_OTP_VERIFY(
-            "helpdesk.otp.verify", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_PASSWORD_MASKVALUE(
     HELPDESK_PASSWORD_MASKVALUE(
             "helpdesk.setPassword.maskValue", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
             "helpdesk.setPassword.maskValue", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_VERIFICATION_METHODS(
     HELPDESK_VERIFICATION_METHODS(
@@ -1074,6 +1072,8 @@ public enum PwmSetting {
             "challenge.requireResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
             "challenge.requireResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
     FORGOTTEN_PASSWORD_REQUIRE_OTP(
     FORGOTTEN_PASSWORD_REQUIRE_OTP(
             "recovery.require.otp", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
             "recovery.require.otp", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
+    HELPDESK_ENABLE_OTP_VERIFY(
+            "helpdesk.otp.verify", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
 
 
 
 
 
 

+ 6 - 6
src/main/java/password/pwm/config/option/RecoveryVerificationMethods.java → src/main/java/password/pwm/config/option/IdentityVerificationMethod.java

@@ -30,7 +30,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 
 
-public enum RecoveryVerificationMethods implements ConfigurationOption {
+public enum IdentityVerificationMethod implements ConfigurationOption {
     PREVIOUS_AUTH(      false,  Display.Field_VerificationMethodPreviousAuth),
     PREVIOUS_AUTH(      false,  Display.Field_VerificationMethodPreviousAuth),
     ATTRIBUTES(         true,   Display.Field_VerificationMethodAttributes),
     ATTRIBUTES(         true,   Display.Field_VerificationMethodAttributes),
     CHALLENGE_RESPONSES(true,   Display.Field_VerificationMethodChallengeResponses),
     CHALLENGE_RESPONSES(true,   Display.Field_VerificationMethodChallengeResponses),
@@ -44,7 +44,7 @@ public enum RecoveryVerificationMethods implements ConfigurationOption {
     private final boolean userSelectable;
     private final boolean userSelectable;
     private final Display displayKey;
     private final Display displayKey;
 
 
-    RecoveryVerificationMethods(boolean userSelectable, Display displayKey) {
+    IdentityVerificationMethod(boolean userSelectable, Display displayKey) {
         this.userSelectable = userSelectable;
         this.userSelectable = userSelectable;
         this.displayKey = displayKey;
         this.displayKey = displayKey;
     }
     }
@@ -61,10 +61,10 @@ public enum RecoveryVerificationMethods implements ConfigurationOption {
         return Display.getLocalizedMessage(locale, this.getDisplayKey(), configuration);
         return Display.getLocalizedMessage(locale, this.getDisplayKey(), configuration);
     }
     }
 
 
-    public static RecoveryVerificationMethods[] availableValues() {
-        final List<RecoveryVerificationMethods> values = new ArrayList<>();
-        values.addAll(Arrays.asList(RecoveryVerificationMethods.values()));
-        return values.toArray(new RecoveryVerificationMethods[values.size()]);
+    public static IdentityVerificationMethod[] availableValues() {
+        final List<IdentityVerificationMethod> values = new ArrayList<>();
+        values.addAll(Arrays.asList(IdentityVerificationMethod.values()));
+        return values.toArray(new IdentityVerificationMethod[values.size()]);
     }
     }
 
 
 }
 }

+ 4 - 4
src/main/java/password/pwm/config/profile/AbstractProfile.java

@@ -23,7 +23,7 @@
 package password.pwm.config.profile;
 package password.pwm.config.profile;
 
 
 import password.pwm.config.*;
 import password.pwm.config.*;
-import password.pwm.config.option.RecoveryVerificationMethods;
+import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.VerificationMethodValue;
 import password.pwm.config.value.VerificationMethodValue;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
@@ -128,12 +128,12 @@ public abstract class AbstractProfile implements Profile, SettingReader {
         return valueMap;
         return valueMap;
     }
     }
 
 
-    public Set<RecoveryVerificationMethods> readVerificationMethods(final PwmSetting pwmSetting, VerificationMethodValue.EnabledState enabledState) {
-        final Set<RecoveryVerificationMethods> result = new LinkedHashSet<>();
+    public Set<IdentityVerificationMethod> readVerificationMethods(final PwmSetting pwmSetting, VerificationMethodValue.EnabledState enabledState) {
+        final Set<IdentityVerificationMethod> result = new LinkedHashSet<>();
         final StoredValue configValue = storedValueMap.get(pwmSetting);
         final StoredValue configValue = storedValueMap.get(pwmSetting);
         final VerificationMethodValue.VerificationMethodSettings verificationMethodSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
         final VerificationMethodValue.VerificationMethodSettings verificationMethodSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
 
 
-        for (final RecoveryVerificationMethods recoveryVerificationMethods : RecoveryVerificationMethods.availableValues()) {
+        for (final IdentityVerificationMethod recoveryVerificationMethods : IdentityVerificationMethod.availableValues()) {
             if (verificationMethodSettings.getMethodSettings().containsKey(recoveryVerificationMethods)) {
             if (verificationMethodSettings.getMethodSettings().containsKey(recoveryVerificationMethods)) {
                 if (verificationMethodSettings.getMethodSettings().get(recoveryVerificationMethods).getEnabledState() == enabledState) {
                 if (verificationMethodSettings.getMethodSettings().get(recoveryVerificationMethods).getEnabledState() == enabledState) {
                     result.add(recoveryVerificationMethods);
                     result.add(recoveryVerificationMethods);

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

@@ -23,7 +23,7 @@
 package password.pwm.config.profile;
 package password.pwm.config.profile;
 
 
 import password.pwm.config.*;
 import password.pwm.config.*;
-import password.pwm.config.option.RecoveryVerificationMethods;
+import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.VerificationMethodValue;
 import password.pwm.config.value.VerificationMethodValue;
 
 
@@ -31,8 +31,8 @@ import java.util.*;
 
 
 public class ForgottenPasswordProfile extends AbstractProfile {
 public class ForgottenPasswordProfile extends AbstractProfile {
 
 
-    private Set<RecoveryVerificationMethods> requiredRecoveryVerificationMethods;
-    private Set<RecoveryVerificationMethods> optionalRecoveryVerificationMethods;
+    private Set<IdentityVerificationMethod> requiredRecoveryVerificationMethods;
+    private Set<IdentityVerificationMethod> optionalRecoveryVerificationMethods;
 
 
     public ForgottenPasswordProfile(String identifier, Map<PwmSetting, StoredValue> storedValueMap) {
     public ForgottenPasswordProfile(String identifier, Map<PwmSetting, StoredValue> storedValueMap) {
         super(identifier, storedValueMap);
         super(identifier, storedValueMap);
@@ -59,21 +59,21 @@ public class ForgottenPasswordProfile extends AbstractProfile {
         return ProfileType.ForgottenPassword;
         return ProfileType.ForgottenPassword;
     }
     }
     
     
-    public Set<RecoveryVerificationMethods> requiredRecoveryAuthenticationMethods() {
+    public Set<IdentityVerificationMethod> requiredRecoveryAuthenticationMethods() {
         if (requiredRecoveryVerificationMethods == null) {
         if (requiredRecoveryVerificationMethods == null) {
             requiredRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.required);
             requiredRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.required);
         }
         }
         return requiredRecoveryVerificationMethods;
         return requiredRecoveryVerificationMethods;
     }
     }
 
 
-    public Set<RecoveryVerificationMethods> optionalRecoveryAuthenticationMethods() {
+    public Set<IdentityVerificationMethod> optionalRecoveryAuthenticationMethods() {
         if (optionalRecoveryVerificationMethods == null) {
         if (optionalRecoveryVerificationMethods == null) {
             optionalRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.optional);
             optionalRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.optional);
         }
         }
         return optionalRecoveryVerificationMethods;
         return optionalRecoveryVerificationMethods;
     }
     }
     
     
-    private Set<RecoveryVerificationMethods> readRecoveryAuthMethods(final VerificationMethodValue.EnabledState enabledState) {
+    private Set<IdentityVerificationMethod> readRecoveryAuthMethods(final VerificationMethodValue.EnabledState enabledState) {
         return this.readVerificationMethods(PwmSetting.RECOVERY_VERIFICATION_METHODS, enabledState);
         return this.readVerificationMethods(PwmSetting.RECOVERY_VERIFICATION_METHODS, enabledState);
     }
     }
 
 

+ 5 - 5
src/main/java/password/pwm/config/profile/HelpdeskProfile.java

@@ -24,7 +24,7 @@ package password.pwm.config.profile;
 
 
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
-import password.pwm.config.option.RecoveryVerificationMethods;
+import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.VerificationMethodValue;
 import password.pwm.config.value.VerificationMethodValue;
 
 
@@ -54,14 +54,14 @@ public class HelpdeskProfile extends AbstractProfile implements Profile {
         return PROFILE_TYPE;
         return PROFILE_TYPE;
     }
     }
 
 
-    public  Collection<RecoveryVerificationMethods> readOptionalVerificationMethods() {
-        final Set<RecoveryVerificationMethods> result = new LinkedHashSet<>();
-        result.addAll(readVerificationMethods(PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.optional));
+    public Collection<IdentityVerificationMethod> readOptionalVerificationMethods() {
+        final Set<IdentityVerificationMethod> result = new LinkedHashSet<>();
         result.addAll(readVerificationMethods(PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.optional));
         result.addAll(readVerificationMethods(PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.optional));
+        result.addAll(readVerificationMethods(PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.required));
         return Collections.unmodifiableSet(result);
         return Collections.unmodifiableSet(result);
     }
     }
 
 
-    public  Collection<RecoveryVerificationMethods> readRequiredVerificationMethods() {
+    public Collection<IdentityVerificationMethod> readRequiredVerificationMethods() {
         return readVerificationMethods(PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.required);
         return readVerificationMethods(PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.required);
     }
     }
 
 

+ 11 - 46
src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java

@@ -22,32 +22,23 @@
 
 
 package password.pwm.config.profile;
 package password.pwm.config.profile;
 
 
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
+import com.novell.ldapchai.ChaiPasswordPolicy;
+import com.novell.ldapchai.ChaiPasswordRule;
+import com.novell.ldapchai.util.DefaultChaiPasswordPolicy;
+import com.novell.ldapchai.util.PasswordRuleHelper;
+import com.novell.ldapchai.util.StringHelper;
 import password.pwm.config.UserPermission;
 import password.pwm.config.UserPermission;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
 import password.pwm.health.HealthRecord;
 import password.pwm.util.Helper;
 import password.pwm.util.Helper;
+import password.pwm.util.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroMachine;
 
 
-import com.novell.ldapchai.ChaiPasswordPolicy;
-import com.novell.ldapchai.ChaiPasswordRule;
-import com.novell.ldapchai.util.DefaultChaiPasswordPolicy;
-import com.novell.ldapchai.util.PasswordRuleHelper;
-import com.novell.ldapchai.util.StringHelper;
+import java.io.Serializable;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 
 
 
 
 /**
 /**
@@ -123,33 +114,7 @@ public class PwmPasswordPolicy implements Profile,Serializable {
 
 
     @Override
     @Override
     public String toString() {
     public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("PwmPasswordPolicy");
-        sb.append(": ");
-
-        final List<String> outputList = new ArrayList<>();
-        for (final String key : policyMap.keySet()) {
-            final PwmPasswordRule rule = PwmPasswordRule.forKey(key);
-            if (rule != null) {
-                switch (rule) {
-                    case DisallowedAttributes:
-                    case DisallowedValues:
-                        outputList.add(rule + "=[" + StringHelper.stringCollectionToString(StringHelper.tokenizeString(policyMap.get(key), "\n"), ", ") + "]");
-                        break;
-                    default:
-                        outputList.add(rule + "=" + policyMap.get(key));
-                        break;
-                }
-            } else {
-                outputList.add(key + "=" + policyMap.get(key));
-            }
-        }
-
-        sb.append("{");
-        sb.append(StringHelper.stringCollectionToString(outputList, ", "));
-        sb.append("}");
-
-        return sb.toString();
+        return "PwmPasswordPolicy" + ": " + JsonUtil.serialize(this);
     }
     }
 
 
     public ChaiPasswordPolicy getChaiPasswordPolicy() {
     public ChaiPasswordPolicy getChaiPasswordPolicy() {

+ 31 - 9
src/main/java/password/pwm/config/value/VerificationMethodValue.java

@@ -26,9 +26,11 @@ import org.jdom2.CDATA;
 import org.jdom2.Element;
 import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
-import password.pwm.config.option.RecoveryVerificationMethods;
+import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
+import password.pwm.i18n.Display;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.JsonUtil;
+import password.pwm.util.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 
 
@@ -48,18 +50,18 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
     }
     }
 
 
     public static class VerificationMethodSettings implements Serializable {
     public static class VerificationMethodSettings implements Serializable {
-        private Map<RecoveryVerificationMethods,VerificationMethodSetting> methodSettings = new HashMap<>();
+        private Map<IdentityVerificationMethod,VerificationMethodSetting> methodSettings = new HashMap<>();
         private int minOptionalRequired = 0;
         private int minOptionalRequired = 0;
 
 
         public VerificationMethodSettings() {
         public VerificationMethodSettings() {
         }
         }
 
 
-        public VerificationMethodSettings(Map<RecoveryVerificationMethods, VerificationMethodSetting> methodSettings, int minOptionalRequired) {
+        public VerificationMethodSettings(Map<IdentityVerificationMethod, VerificationMethodSetting> methodSettings, int minOptionalRequired) {
             this.methodSettings = methodSettings;
             this.methodSettings = methodSettings;
             this.minOptionalRequired = minOptionalRequired;
             this.minOptionalRequired = minOptionalRequired;
         }
         }
 
 
-        public Map<RecoveryVerificationMethods, VerificationMethodSetting> getMethodSettings() {
+        public Map<IdentityVerificationMethod, VerificationMethodSetting> getMethodSettings() {
             return Collections.unmodifiableMap(methodSettings);
             return Collections.unmodifiableMap(methodSettings);
         }
         }
 
 
@@ -86,7 +88,7 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
 
 
     public VerificationMethodValue(VerificationMethodSettings value) {
     public VerificationMethodValue(VerificationMethodSettings value) {
         this.value = value;
         this.value = value;
-        for (final RecoveryVerificationMethods recoveryVerificationMethods : RecoveryVerificationMethods.availableValues()) {
+        for (final IdentityVerificationMethod recoveryVerificationMethods : IdentityVerificationMethod.availableValues()) {
             if (!value.methodSettings.containsKey(recoveryVerificationMethods)) {
             if (!value.methodSettings.containsKey(recoveryVerificationMethods)) {
                 value.methodSettings.put(recoveryVerificationMethods,new VerificationMethodSetting(EnabledState.disabled));
                 value.methodSettings.put(recoveryVerificationMethods,new VerificationMethodSetting(EnabledState.disabled));
             }
             }
@@ -140,11 +142,31 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
             return "No Verification Methods";
             return "No Verification Methods";
         }
         }
         final StringBuilder out = new StringBuilder();
         final StringBuilder out = new StringBuilder();
-        for (final RecoveryVerificationMethods method : value.getMethodSettings().keySet()) {
-            out.append(" ").append(method.toString()).append(": ").append(value.getMethodSettings().get(method).getEnabledState());
-            out.append("\n");
+        final List<String> optionals = new ArrayList<>();
+        final List<String> required = new ArrayList<>();
+        for (final IdentityVerificationMethod method : value.getMethodSettings().keySet()) {
+            switch (value.getMethodSettings().get(method).getEnabledState()) {
+                case optional:
+                    optionals.add(method.getLabel(null, locale));
+                    break;
+
+                case required:
+                    required.add(method.getLabel(null, locale));
+                    break;
+            }
+            method.getLabel(null,locale);
         }
         }
-        out.append("  Minimum Optional Methods Required: ").append(value.getMinOptionalRequired());
+
+        out.append("optional methods: ").append(optionals.isEmpty()
+                        ? LocaleHelper.getLocalizedMessage(locale, Display.Value_NotApplicable, null)
+                        : JsonUtil.serializeCollection(optionals)
+        );
+        out.append(", required methods: ").append(required.isEmpty()
+                        ? LocaleHelper.getLocalizedMessage(locale, Display.Value_NotApplicable, null)
+                        : JsonUtil.serializeCollection(required)
+        );
+
+        out.append(",  minimum optional methods required: ").append(value.getMinOptionalRequired());
         return out.toString();
         return out.toString();
     }
     }
 }
 }

+ 1 - 0
src/main/java/password/pwm/http/PwmRequest.java

@@ -273,6 +273,7 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         HelpdeskDetail,
         HelpdeskDetail,
         HelpdeskObfuscatedDN,
         HelpdeskObfuscatedDN,
         HelpdeskUsername,
         HelpdeskUsername,
+        HelpdeskVerificationEnabled,
 
 
         ConfigFilename,
         ConfigFilename,
         ConfigLastModified,
         ConfigLastModified,

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

@@ -24,11 +24,11 @@ package password.pwm.http.bean;
 
 
 import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.cr.ResponseSet;
 import com.novell.ldapchai.cr.ResponseSet;
-import password.pwm.RecoveryVerificationMethod;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.MessageSendMethod;
-import password.pwm.config.option.RecoveryVerificationMethods;
+import password.pwm.config.option.IdentityVerificationMethod;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.*;
 import java.util.*;
@@ -122,16 +122,16 @@ public class ForgottenPasswordBean extends PwmSessionBean {
     public static class Progress implements Serializable {
     public static class Progress implements Serializable {
         private boolean tokenSent;
         private boolean tokenSent;
         private boolean allPassed;
         private boolean allPassed;
-        private final Set<RecoveryVerificationMethods> satisfiedMethods = new HashSet<>();
+        private final Set<IdentityVerificationMethod> satisfiedMethods = new HashSet<>();
 
 
         private MessageSendMethod tokenSendChoice;
         private MessageSendMethod tokenSendChoice;
         private String tokenSentAddress;
         private String tokenSentAddress;
-        private RecoveryVerificationMethods inProgressVerificationMethod;
+        private IdentityVerificationMethod inProgressVerificationMethod;
 
 
-        private transient RecoveryVerificationMethod naafRecoveryMethod;
-        private transient RecoveryVerificationMethod remoteRecoveryMethod;
+        private transient VerificationMethodSystem naafRecoveryMethod;
+        private transient VerificationMethodSystem remoteRecoveryMethod;
 
 
-        public Set<RecoveryVerificationMethods> getSatisfiedMethods() {
+        public Set<IdentityVerificationMethod> getSatisfiedMethods() {
             return satisfiedMethods;
             return satisfiedMethods;
         }
         }
 
 
@@ -175,35 +175,35 @@ public class ForgottenPasswordBean extends PwmSessionBean {
             this.tokenSentAddress = tokenSentAddress;
             this.tokenSentAddress = tokenSentAddress;
         }
         }
 
 
-        public RecoveryVerificationMethods getInProgressVerificationMethod() {
+        public IdentityVerificationMethod getInProgressVerificationMethod() {
             return inProgressVerificationMethod;
             return inProgressVerificationMethod;
         }
         }
 
 
-        public void setInProgressVerificationMethod(RecoveryVerificationMethods inProgressVerificationMethod) {
+        public void setInProgressVerificationMethod(IdentityVerificationMethod inProgressVerificationMethod) {
             this.inProgressVerificationMethod = inProgressVerificationMethod;
             this.inProgressVerificationMethod = inProgressVerificationMethod;
         }
         }
 
 
-        public void setNaafRecoveryMethod(RecoveryVerificationMethod naafRecoveryMethod) {
+        public void setNaafRecoveryMethod(VerificationMethodSystem naafRecoveryMethod) {
             this.naafRecoveryMethod = naafRecoveryMethod;
             this.naafRecoveryMethod = naafRecoveryMethod;
         }
         }
 
 
-        public RecoveryVerificationMethod getNaafRecoveryMethod() {
+        public VerificationMethodSystem getNaafRecoveryMethod() {
             return naafRecoveryMethod;
             return naafRecoveryMethod;
         }
         }
 
 
-        public RecoveryVerificationMethod getRemoteRecoveryMethod() {
+        public VerificationMethodSystem getRemoteRecoveryMethod() {
             return remoteRecoveryMethod;
             return remoteRecoveryMethod;
         }
         }
 
 
-        public void setRemoteRecoveryMethod(RecoveryVerificationMethod remoteRecoveryMethod) {
+        public void setRemoteRecoveryMethod(VerificationMethodSystem remoteRecoveryMethod) {
             this.remoteRecoveryMethod = remoteRecoveryMethod;
             this.remoteRecoveryMethod = remoteRecoveryMethod;
         }
         }
     }
     }
 
 
     public static class RecoveryFlags implements Serializable {
     public static class RecoveryFlags implements Serializable {
         private final boolean allowWhenLdapIntruderLocked;
         private final boolean allowWhenLdapIntruderLocked;
-        private final Set<RecoveryVerificationMethods> requiredAuthMethods;
-        private final Set<RecoveryVerificationMethods> optionalAuthMethods;
+        private final Set<IdentityVerificationMethod> requiredAuthMethods;
+        private final Set<IdentityVerificationMethod> optionalAuthMethods;
         private final int minimumOptionalAuthMethods;
         private final int minimumOptionalAuthMethods;
         private final MessageSendMethod tokenSendMethod;
         private final MessageSendMethod tokenSendMethod;
 
 
@@ -217,8 +217,8 @@ public class ForgottenPasswordBean extends PwmSessionBean {
         }
         }
 
 
         public RecoveryFlags(
         public RecoveryFlags(
-                final Set<RecoveryVerificationMethods> requiredAuthMethods,
-                final Set<RecoveryVerificationMethods> optionalAuthMethods,
+                final Set<IdentityVerificationMethod> requiredAuthMethods,
+                final Set<IdentityVerificationMethod> optionalAuthMethods,
                 final int minimumOptionalAuthMethods,
                 final int minimumOptionalAuthMethods,
                 final boolean allowWhenLdapIntruderLocked,
                 final boolean allowWhenLdapIntruderLocked,
                 final MessageSendMethod tokenSendMethod
                 final MessageSendMethod tokenSendMethod
@@ -231,7 +231,7 @@ public class ForgottenPasswordBean extends PwmSessionBean {
             this.tokenSendMethod = tokenSendMethod;
             this.tokenSendMethod = tokenSendMethod;
         }
         }
 
 
-        public Set<RecoveryVerificationMethods> getRequiredAuthMethods() {
+        public Set<IdentityVerificationMethod> getRequiredAuthMethods() {
             return requiredAuthMethods;
             return requiredAuthMethods;
         }
         }
 
 
@@ -244,7 +244,7 @@ public class ForgottenPasswordBean extends PwmSessionBean {
             return tokenSendMethod;
             return tokenSendMethod;
         }
         }
 
 
-        public Set<RecoveryVerificationMethods> getOptionalAuthMethods() {
+        public Set<IdentityVerificationMethod> getOptionalAuthMethods() {
             return optionalAuthMethods;
             return optionalAuthMethods;
         }
         }
 
 

+ 2 - 0
src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java

@@ -467,6 +467,8 @@ public class DebugItemGenerator {
                 dataRow.add(record.getProfile() == null ? "" : record.getProfile());
                 dataRow.add(record.getProfile() == null ? "" : record.getProfile());
                 csvPrinter.printRecord(dataRow);
                 csvPrinter.printRecord(dataRow);
             }
             }
+            csvPrinter.flush();
+            outputStream.write(byteArrayOutputStream.toByteArray());
         }
         }
     }
     }
 
 

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

@@ -33,12 +33,12 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
-import password.pwm.RecoveryVerificationMethod;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.*;
 import password.pwm.bean.*;
 import password.pwm.config.*;
 import password.pwm.config.*;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.RecoveryAction;
 import password.pwm.config.option.RecoveryAction;
-import password.pwm.config.option.RecoveryVerificationMethods;
+import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.profile.ForgottenPasswordProfile;
 import password.pwm.config.profile.ForgottenPasswordProfile;
 import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.profile.ProfileUtility;
@@ -295,13 +295,13 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
     {
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
         final String requestedChoiceStr = pwmRequest.readParameterAsString("choice");
         final String requestedChoiceStr = pwmRequest.readParameterAsString("choice");
-        final LinkedHashSet<RecoveryVerificationMethods> remainingAvailableOptionalMethods = new LinkedHashSet<>(figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean));
+        final LinkedHashSet<IdentityVerificationMethod> remainingAvailableOptionalMethods = new LinkedHashSet<>(figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean));
         pwmRequest.setAttribute(PwmRequest.Attribute.AvailableAuthMethods, remainingAvailableOptionalMethods);
         pwmRequest.setAttribute(PwmRequest.Attribute.AvailableAuthMethods, remainingAvailableOptionalMethods);
 
 
-        RecoveryVerificationMethods requestedChoice = null;
+        IdentityVerificationMethod requestedChoice = null;
         if (requestedChoiceStr != null && !requestedChoiceStr.isEmpty()) {
         if (requestedChoiceStr != null && !requestedChoiceStr.isEmpty()) {
             try {
             try {
-                requestedChoice = RecoveryVerificationMethods.valueOf(requestedChoiceStr);
+                requestedChoice = IdentityVerificationMethod.valueOf(requestedChoiceStr);
             } catch (IllegalArgumentException e) {
             } catch (IllegalArgumentException e) {
                 final String errorMsg = "unknown verification method requested";
                 final String errorMsg = "unknown verification method requested";
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,errorMsg);
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,errorMsg);
@@ -428,7 +428,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                             forgottenPasswordBean
                             forgottenPasswordBean
                     );
                     );
                 }
                 }
-                forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.TOKEN);
+                forgottenPasswordBean.getProgress().getSatisfiedMethods().add(IdentityVerificationMethod.TOKEN);
                 StatisticsManager.incrementStat(pwmRequest.getPwmApplication(), Statistic.RECOVERY_TOKENS_PASSED);
                 StatisticsManager.incrementStat(pwmRequest.getPwmApplication(), Statistic.RECOVERY_TOKENS_PASSED);
             }
             }
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
@@ -436,7 +436,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
             errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
         }
         }
 
 
-        if (!forgottenPasswordBean.getProgress().getSatisfiedMethods().contains(RecoveryVerificationMethods.TOKEN)) {
+        if (!forgottenPasswordBean.getProgress().getSatisfiedMethods().contains(IdentityVerificationMethod.TOKEN)) {
             if (errorInformation == null) {
             if (errorInformation == null) {
                 errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT);
                 errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT);
             }
             }
@@ -449,7 +449,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
     {
     {
         final String PREFIX = "naaf-";
         final String PREFIX = "naaf-";
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
-        final RecoveryVerificationMethod naafMethod = forgottenPasswordBean.getProgress().getNaafRecoveryMethod();
+        final VerificationMethodSystem naafMethod = forgottenPasswordBean.getProgress().getNaafRecoveryMethod();
 
 
         final Map<String,String> naafResponses = new LinkedHashMap<>();
         final Map<String,String> naafResponses = new LinkedHashMap<>();
         {
         {
@@ -465,11 +465,11 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
         final ErrorInformation errorInformation = naafMethod.respondToPrompts(naafResponses);
         final ErrorInformation errorInformation = naafMethod.respondToPrompts(naafResponses);
 
 
-        if (naafMethod.getVerificationState() == RecoveryVerificationMethod.VerificationState.COMPLETE) {
-            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.NAAF);
+        if (naafMethod.getVerificationState() == VerificationMethodSystem.VerificationState.COMPLETE) {
+            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(IdentityVerificationMethod.NAAF);
         }
         }
 
 
-        if (naafMethod.getVerificationState() == RecoveryVerificationMethod.VerificationState.FAILED) {
+        if (naafMethod.getVerificationState() == VerificationMethodSystem.VerificationState.FAILED) {
             forgottenPasswordBean.getProgress().setNaafRecoveryMethod(null);
             forgottenPasswordBean.getProgress().setNaafRecoveryMethod(null);
             pwmRequest.respondWithError(errorInformation,true);
             pwmRequest.respondWithError(errorInformation,true);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
@@ -488,7 +488,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
     {
     {
         final String PREFIX = "remote-";
         final String PREFIX = "remote-";
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean(pwmRequest);
-        final RecoveryVerificationMethod remoteRecoveryMethod = forgottenPasswordBean.getProgress().getRemoteRecoveryMethod();
+        final VerificationMethodSystem remoteRecoveryMethod = forgottenPasswordBean.getProgress().getRemoteRecoveryMethod();
 
 
         final Map<String,String> remoteResponses = new LinkedHashMap<>();
         final Map<String,String> remoteResponses = new LinkedHashMap<>();
         {
         {
@@ -504,11 +504,11 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
         final ErrorInformation errorInformation = remoteRecoveryMethod.respondToPrompts(remoteResponses);
         final ErrorInformation errorInformation = remoteRecoveryMethod.respondToPrompts(remoteResponses);
 
 
-        if (remoteRecoveryMethod.getVerificationState() == RecoveryVerificationMethod.VerificationState.COMPLETE) {
-            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.REMOTE_RESPONSES);
+        if (remoteRecoveryMethod.getVerificationState() == VerificationMethodSystem.VerificationState.COMPLETE) {
+            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(IdentityVerificationMethod.REMOTE_RESPONSES);
         }
         }
 
 
-        if (remoteRecoveryMethod.getVerificationState() == RecoveryVerificationMethod.VerificationState.FAILED) {
+        if (remoteRecoveryMethod.getVerificationState() == VerificationMethodSystem.VerificationState.FAILED) {
             forgottenPasswordBean.getProgress().setNaafRecoveryMethod(null);
             forgottenPasswordBean.getProgress().setNaafRecoveryMethod(null);
             pwmRequest.respondWithError(errorInformation,true);
             pwmRequest.respondWithError(errorInformation,true);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
@@ -546,7 +546,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                 if (otpPassed) {
                 if (otpPassed) {
                     StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_OTP_PASSED);
                     StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_OTP_PASSED);
                     LOGGER.debug(pwmRequest, "one time password validation has been passed");
                     LOGGER.debug(pwmRequest, "one time password validation has been passed");
-                    forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.OTP);
+                    forgottenPasswordBean.getProgress().getSatisfiedMethods().add(IdentityVerificationMethod.OTP);
                 } else {
                 } else {
                     StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_OTP_FAILED);
                     StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_OTP_FAILED);
                     handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_OTP_TOKEN));
                     handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_OTP_TOKEN));
@@ -610,7 +610,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             return;
             return;
         }
         }
 
 
-        forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.CHALLENGE_RESPONSES);
+        forgottenPasswordBean.getProgress().getSatisfiedMethods().add(IdentityVerificationMethod.CHALLENGE_RESPONSES);
     }
     }
 
 
     private void processCheckAttributes(final PwmRequest pwmRequest)
     private void processCheckAttributes(final PwmRequest pwmRequest)
@@ -651,7 +651,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                 }
                 }
             }
             }
 
 
-            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.ATTRIBUTES);
+            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(IdentityVerificationMethod.ATTRIBUTES);
         } catch (PwmDataValidationException e) {
         } catch (PwmDataValidationException e) {
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE,e.getErrorInformation().toDebugStr()));
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE,e.getErrorInformation().toDebugStr()));
         }
         }
@@ -691,17 +691,17 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
 
 
         // check for previous authentication
         // check for previous authentication
-        if (recoveryFlags.getRequiredAuthMethods().contains(RecoveryVerificationMethods.PREVIOUS_AUTH) || recoveryFlags.getOptionalAuthMethods().contains(RecoveryVerificationMethods.PREVIOUS_AUTH)) {
-            if (!progress.getSatisfiedMethods().contains(RecoveryVerificationMethods.PREVIOUS_AUTH)) {
+        if (recoveryFlags.getRequiredAuthMethods().contains(IdentityVerificationMethod.PREVIOUS_AUTH) || recoveryFlags.getOptionalAuthMethods().contains(IdentityVerificationMethod.PREVIOUS_AUTH)) {
+            if (!progress.getSatisfiedMethods().contains(IdentityVerificationMethod.PREVIOUS_AUTH)) {
                 if (checkAuthRecord(pwmRequest, forgottenPasswordBean.getUserInfo().getUserGuid())) {
                 if (checkAuthRecord(pwmRequest, forgottenPasswordBean.getUserInfo().getUserGuid())) {
-                    LOGGER.debug(pwmRequest, "marking " + RecoveryVerificationMethods.PREVIOUS_AUTH + " method as satisfied");
-                    progress.getSatisfiedMethods().add(RecoveryVerificationMethods.PREVIOUS_AUTH);
+                    LOGGER.debug(pwmRequest, "marking " + IdentityVerificationMethod.PREVIOUS_AUTH + " method as satisfied");
+                    progress.getSatisfiedMethods().add(IdentityVerificationMethod.PREVIOUS_AUTH);
                 }
                 }
             }
             }
         }
         }
 
 
         // dispatch required auth methods.
         // dispatch required auth methods.
-        for (final RecoveryVerificationMethods method : recoveryFlags.getRequiredAuthMethods()) {
+        for (final IdentityVerificationMethod method : recoveryFlags.getRequiredAuthMethods()) {
             if (!progress.getSatisfiedMethods().contains(method)) {
             if (!progress.getSatisfiedMethods().contains(method)) {
                 forwardUserBasedOnRecoveryMethod(pwmRequest, method);
                 forwardUserBasedOnRecoveryMethod(pwmRequest, method);
                 return;
                 return;
@@ -721,9 +721,9 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
         // check if more optional methods required
         // check if more optional methods required
         if (recoveryFlags.getMinimumOptionalAuthMethods() > 0) {
         if (recoveryFlags.getMinimumOptionalAuthMethods() > 0) {
-            final Set<RecoveryVerificationMethods> satisfiedOptionalMethods = figureSatisfiedOptionalAuthMethods(recoveryFlags,progress);
+            final Set<IdentityVerificationMethod> satisfiedOptionalMethods = figureSatisfiedOptionalAuthMethods(recoveryFlags,progress);
             if (satisfiedOptionalMethods.size() < recoveryFlags.getMinimumOptionalAuthMethods()) {
             if (satisfiedOptionalMethods.size() < recoveryFlags.getMinimumOptionalAuthMethods()) {
-                final Set<RecoveryVerificationMethods> remainingAvailableOptionalMethods = figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean);
+                final Set<IdentityVerificationMethod> remainingAvailableOptionalMethods = figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean);
                 if (remainingAvailableOptionalMethods.isEmpty()) {
                 if (remainingAvailableOptionalMethods.isEmpty()) {
                     final String errorMsg = "additional optional verification methods are needed, however all available optional verification methods have been satisified by user";
                     final String errorMsg = "additional optional verification methods are needed, however all available optional verification methods have been satisified by user";
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,errorMsg);
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,errorMsg);
@@ -731,7 +731,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                     throw new PwmUnrecoverableException(errorInformation);
                     throw new PwmUnrecoverableException(errorInformation);
                 } else {
                 } else {
                     if (remainingAvailableOptionalMethods.size() == 1) {
                     if (remainingAvailableOptionalMethods.size() == 1) {
-                        final RecoveryVerificationMethods remainingMethod = remainingAvailableOptionalMethods.iterator().next();
+                        final IdentityVerificationMethod remainingMethod = remainingAvailableOptionalMethods.iterator().next();
                         LOGGER.debug(pwmRequest, "only 1 remaining available optional verification method, will redirect to " + remainingMethod.toString());
                         LOGGER.debug(pwmRequest, "only 1 remaining available optional verification method, will redirect to " + remainingMethod.toString());
                         forwardUserBasedOnRecoveryMethod(pwmRequest, remainingMethod);
                         forwardUserBasedOnRecoveryMethod(pwmRequest, remainingMethod);
                         progress.setInProgressVerificationMethod(remainingMethod);
                         progress.setInProgressVerificationMethod(remainingMethod);
@@ -1134,7 +1134,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
     private static void verifyRequirementsForAuthMethod(
     private static void verifyRequirementsForAuthMethod(
             final ForgottenPasswordBean forgottenPasswordBean,
             final ForgottenPasswordBean forgottenPasswordBean,
-            final RecoveryVerificationMethods recoveryVerificationMethods) throws PwmUnrecoverableException
+            final IdentityVerificationMethod recoveryVerificationMethods) throws PwmUnrecoverableException
     {
     {
         switch (recoveryVerificationMethods) {
         switch (recoveryVerificationMethods) {
             case TOKEN: {
             case TOKEN: {
@@ -1228,8 +1228,8 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
         final ResponseSet responseSet;
         final ResponseSet responseSet;
         final ChallengeSet challengeSet;
         final ChallengeSet challengeSet;
-        if (recoveryFlags.getRequiredAuthMethods().contains(RecoveryVerificationMethods.CHALLENGE_RESPONSES)
-                || recoveryFlags.getOptionalAuthMethods().contains(RecoveryVerificationMethods.CHALLENGE_RESPONSES)) {
+        if (recoveryFlags.getRequiredAuthMethods().contains(IdentityVerificationMethod.CHALLENGE_RESPONSES)
+                || recoveryFlags.getOptionalAuthMethods().contains(IdentityVerificationMethod.CHALLENGE_RESPONSES)) {
             try {
             try {
                 final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userInfoBean.getUserIdentity());
                 final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userInfoBean.getUserIdentity());
                 responseSet = pwmApplication.getCrService().readUserResponseSet(
                 responseSet = pwmApplication.getCrService().readUserResponseSet(
@@ -1283,7 +1283,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         forgottenPasswordBean.setRecoveryFlags(recoveryFlags);
         forgottenPasswordBean.setRecoveryFlags(recoveryFlags);
         forgottenPasswordBean.setProgress(new ForgottenPasswordBean.Progress());
         forgottenPasswordBean.setProgress(new ForgottenPasswordBean.Progress());
 
 
-        for (final RecoveryVerificationMethods recoveryVerificationMethods : recoveryFlags.getRequiredAuthMethods()) {
+        for (final IdentityVerificationMethod recoveryVerificationMethods : recoveryFlags.getRequiredAuthMethods()) {
             verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethods);
             verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethods);
         }
         }
     }
     }
@@ -1297,8 +1297,8 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
         final MessageSendMethod tokenSendMethod = config.getForgottenPasswordProfiles().get(forgottenPasswordProfileID).readSettingAsEnum(PwmSetting.RECOVERY_TOKEN_SEND_METHOD, MessageSendMethod.class);
         final MessageSendMethod tokenSendMethod = config.getForgottenPasswordProfiles().get(forgottenPasswordProfileID).readSettingAsEnum(PwmSetting.RECOVERY_TOKEN_SEND_METHOD, MessageSendMethod.class);
 
 
-        final Set<RecoveryVerificationMethods> requiredRecoveryVerificationMethods = forgottenPasswordProfile.requiredRecoveryAuthenticationMethods();
-        final Set<RecoveryVerificationMethods> optionalRecoveryVerificationMethods = forgottenPasswordProfile.optionalRecoveryAuthenticationMethods();
+        final Set<IdentityVerificationMethod> requiredRecoveryVerificationMethods = forgottenPasswordProfile.requiredRecoveryAuthenticationMethods();
+        final Set<IdentityVerificationMethod> optionalRecoveryVerificationMethods = forgottenPasswordProfile.optionalRecoveryAuthenticationMethods();
         final int minimumOptionalRecoveryAuthMethods = forgottenPasswordProfile.getMinOptionalRequired();
         final int minimumOptionalRecoveryAuthMethods = forgottenPasswordProfile.getMinOptionalRequired();
         final boolean allowWhenLdapIntruderLocked = forgottenPasswordProfile.readSettingAsBoolean(PwmSetting.RECOVERY_ALLOW_WHEN_LOCKED);
         final boolean allowWhenLdapIntruderLocked = forgottenPasswordProfile.readSettingAsBoolean(PwmSetting.RECOVERY_ALLOW_WHEN_LOCKED);
 
 
@@ -1404,27 +1404,27 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
         throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT));
         throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT));
     }
     }
 
 
-    private static Set<RecoveryVerificationMethods> figureSatisfiedOptionalAuthMethods(
+    private static Set<IdentityVerificationMethod> figureSatisfiedOptionalAuthMethods(
             ForgottenPasswordBean.RecoveryFlags recoveryFlags,
             ForgottenPasswordBean.RecoveryFlags recoveryFlags,
             ForgottenPasswordBean.Progress progress)
             ForgottenPasswordBean.Progress progress)
     {
     {
-        final Set<RecoveryVerificationMethods> result = new HashSet<>();
+        final Set<IdentityVerificationMethod> result = new HashSet<>();
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.retainAll(progress.getSatisfiedMethods());
         result.retainAll(progress.getSatisfiedMethods());
         return Collections.unmodifiableSet(result);
         return Collections.unmodifiableSet(result);
     }
     }
 
 
-    private static Set<RecoveryVerificationMethods> figureRemainingAvailableOptionalAuthMethods(
+    private static Set<IdentityVerificationMethod> figureRemainingAvailableOptionalAuthMethods(
             final ForgottenPasswordBean forgottenPasswordBean
             final ForgottenPasswordBean forgottenPasswordBean
     )
     )
     {
     {
         ForgottenPasswordBean.RecoveryFlags recoveryFlags = forgottenPasswordBean.getRecoveryFlags();
         ForgottenPasswordBean.RecoveryFlags recoveryFlags = forgottenPasswordBean.getRecoveryFlags();
         ForgottenPasswordBean.Progress progress = forgottenPasswordBean.getProgress();
         ForgottenPasswordBean.Progress progress = forgottenPasswordBean.getProgress();
-        final Set<RecoveryVerificationMethods> result = new HashSet<>();
+        final Set<IdentityVerificationMethod> result = new HashSet<>();
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.removeAll(progress.getSatisfiedMethods());
         result.removeAll(progress.getSatisfiedMethods());
 
 
-        for (final RecoveryVerificationMethods recoveryVerificationMethods : new HashSet<>(result)) {
+        for (final IdentityVerificationMethod recoveryVerificationMethods : new HashSet<>(result)) {
             try {
             try {
                 verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethods);
                 verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethods);
             } catch (PwmUnrecoverableException e) {
             } catch (PwmUnrecoverableException e) {
@@ -1443,7 +1443,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
     private void forwardUserBasedOnRecoveryMethod(
     private void forwardUserBasedOnRecoveryMethod(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final RecoveryVerificationMethods method)
+            final IdentityVerificationMethod method)
             throws ServletException, PwmUnrecoverableException, IOException
             throws ServletException, PwmUnrecoverableException, IOException
     {
     {
         LOGGER.debug(pwmRequest,"attempting to forward request to handle verification method " + method.toString());
         LOGGER.debug(pwmRequest,"attempting to forward request to handle verification method " + method.toString());
@@ -1487,7 +1487,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                     progress.setTokenSent(true);
                     progress.setTokenSent(true);
                 }
                 }
 
 
-                if (!progress.getSatisfiedMethods().contains(RecoveryVerificationMethods.TOKEN)) {
+                if (!progress.getSatisfiedMethods().contains(IdentityVerificationMethod.TOKEN)) {
                     pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_ENTER_TOKEN);
                     pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_ENTER_TOKEN);
                     return;
                     return;
                 }
                 }
@@ -1495,7 +1495,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             break;
             break;
 
 
             case REMOTE_RESPONSES: {
             case REMOTE_RESPONSES: {
-                final RecoveryVerificationMethod remoteMethod;
+                final VerificationMethodSystem remoteMethod;
                 if (forgottenPasswordBean.getProgress().getRemoteRecoveryMethod() == null) {
                 if (forgottenPasswordBean.getProgress().getRemoteRecoveryMethod() == null) {
                     remoteMethod = new RemoteVerificationMethod();
                     remoteMethod = new RemoteVerificationMethod();
                     remoteMethod.init(
                     remoteMethod.init(
@@ -1509,7 +1509,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                     remoteMethod = forgottenPasswordBean.getProgress().getRemoteRecoveryMethod();
                     remoteMethod = forgottenPasswordBean.getProgress().getRemoteRecoveryMethod();
                 }
                 }
 
 
-                final List<RecoveryVerificationMethod.UserPrompt> prompts = remoteMethod.getCurrentPrompts();
+                final List<VerificationMethodSystem.UserPrompt> prompts = remoteMethod.getCurrentPrompts();
                 final String displayInstructions = remoteMethod.getCurrentDisplayInstructions();
                 final String displayInstructions = remoteMethod.getCurrentDisplayInstructions();
 
 
                 pwmRequest.setAttribute(PwmRequest.Attribute.ForgottenPasswordPrompts, new ArrayList<>(prompts));
                 pwmRequest.setAttribute(PwmRequest.Attribute.ForgottenPasswordPrompts, new ArrayList<>(prompts));
@@ -1520,7 +1520,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
 
 
 
 
             case NAAF: {
             case NAAF: {
-                final RecoveryVerificationMethod naafMethod;
+                final VerificationMethodSystem naafMethod;
                 if (forgottenPasswordBean.getProgress().getNaafRecoveryMethod() == null) {
                 if (forgottenPasswordBean.getProgress().getNaafRecoveryMethod() == null) {
                     naafMethod = new PwmNAAFVerificationMethod();
                     naafMethod = new PwmNAAFVerificationMethod();
                     naafMethod.init(
                     naafMethod.init(
@@ -1534,7 +1534,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
                     naafMethod = forgottenPasswordBean.getProgress().getNaafRecoveryMethod();
                     naafMethod = forgottenPasswordBean.getProgress().getNaafRecoveryMethod();
                 }
                 }
 
 
-                final List<RecoveryVerificationMethod.UserPrompt> prompts = naafMethod.getCurrentPrompts();
+                final List<VerificationMethodSystem.UserPrompt> prompts = naafMethod.getCurrentPrompts();
                 final String displayInstructions = naafMethod.getCurrentDisplayInstructions();
                 final String displayInstructions = naafMethod.getCurrentDisplayInstructions();
 
 
                 pwmRequest.setAttribute(PwmRequest.Attribute.ForgottenPasswordPrompts, new ArrayList<>(prompts));
                 pwmRequest.setAttribute(PwmRequest.Attribute.ForgottenPasswordPrompts, new ArrayList<>(prompts));

+ 2 - 2
src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java

@@ -24,7 +24,7 @@ package password.pwm.http.servlet.forgottenpw;
 
 
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
-import password.pwm.RecoveryVerificationMethod;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.*;
 import password.pwm.bean.*;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
@@ -43,7 +43,7 @@ import password.pwm.util.secure.PwmRandom;
 
 
 import java.util.*;
 import java.util.*;
 
 
-public class RemoteVerificationMethod implements RecoveryVerificationMethod {
+public class RemoteVerificationMethod implements VerificationMethodSystem {
 
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(RemoteVerificationMethod.class);
     private static final PwmLogger LOGGER = PwmLogger.forClass(RemoteVerificationMethod.class);
 
 

+ 11 - 0
src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientDataBean.java

@@ -25,8 +25,10 @@ package password.pwm.http.servlet.helpdesk;
 import password.pwm.config.option.HelpdeskClearResponseMode;
 import password.pwm.config.option.HelpdeskClearResponseMode;
 import password.pwm.config.option.HelpdeskUIMode;
 import password.pwm.config.option.HelpdeskUIMode;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.MessageSendMethod;
+import password.pwm.config.option.IdentityVerificationMethod;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 
 
@@ -37,6 +39,7 @@ public class HelpdeskClientDataBean implements Serializable {
     private HelpdeskUIMode helpdesk_setting_PwUiMode;
     private HelpdeskUIMode helpdesk_setting_PwUiMode;
     private MessageSendMethod helpdesk_setting_tokenSendMethod;
     private MessageSendMethod helpdesk_setting_tokenSendMethod;
     private Map<String, ActionInformation> actions = new HashMap<>();
     private Map<String, ActionInformation> actions = new HashMap<>();
+    private Map<String, Collection<IdentityVerificationMethod>> verificationMethods = new HashMap<>();
 
 
     public Map<String, String> getHelpdesk_search_columns() {
     public Map<String, String> getHelpdesk_search_columns() {
         return helpdesk_search_columns;
         return helpdesk_search_columns;
@@ -86,6 +89,14 @@ public class HelpdeskClientDataBean implements Serializable {
         this.actions = actions;
         this.actions = actions;
     }
     }
 
 
+    public Map<String, Collection<IdentityVerificationMethod>> getVerificationMethods() {
+        return verificationMethods;
+    }
+
+    public void setVerificationMethods(Map<String, Collection<IdentityVerificationMethod>> verificationMethods) {
+        this.verificationMethods = verificationMethods;
+    }
+
     public static class ActionInformation implements Serializable {
     public static class ActionInformation implements Serializable {
         private String name;
         private String name;
         private String description;
         private String description;

+ 141 - 17
src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -32,13 +32,13 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.EmailItemBean;
-import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.*;
 import password.pwm.config.*;
 import password.pwm.config.option.HelpdeskClearResponseMode;
 import password.pwm.config.option.HelpdeskClearResponseMode;
 import password.pwm.config.option.HelpdeskUIMode;
 import password.pwm.config.option.HelpdeskUIMode;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.MessageSendMethod;
+import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.error.*;
 import password.pwm.error.*;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
@@ -102,7 +102,8 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         sendVerificationToken(HttpMethod.POST),
         sendVerificationToken(HttpMethod.POST),
         verifyVerificationToken(HttpMethod.POST),
         verifyVerificationToken(HttpMethod.POST),
         clientData(HttpMethod.GET),
         clientData(HttpMethod.GET),
-
+        checkVerification(HttpMethod.POST),
+        showVerifications(HttpMethod.POST),
         ;
         ;
 
 
         private final HttpMethod method;
         private final HttpMethod method;
@@ -133,7 +134,6 @@ public class HelpdeskServlet extends AbstractPwmServlet {
     {
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
 
 
         if (!pwmRequest.isAuthenticated()) {
         if (!pwmRequest.isAuthenticated()) {
             pwmRequest.respondWithError(PwmError.ERROR_AUTHENTICATION_REQUIRED.toInfo());
             pwmRequest.respondWithError(PwmError.ERROR_AUTHENTICATION_REQUIRED.toInfo());
@@ -200,9 +200,18 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                 case clientData:
                 case clientData:
                     restClientData(pwmRequest, helpdeskProfile);
                     restClientData(pwmRequest, helpdeskProfile);
                     return;
                     return;
+
+                case checkVerification:
+                    restCheckVerification(pwmRequest, helpdeskProfile);
+                    return;
+
+                case showVerifications:
+                    restShowVerifications(pwmRequest);
+                    return;
             }
             }
         }
         }
 
 
+        pwmRequest.setAttribute(PwmRequest.Attribute.HelpdeskVerificationEnabled, !helpdeskProfile.readRequiredVerificationMethods().isEmpty());
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_SEARCH);
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_SEARCH);
     }
     }
 
 
@@ -235,6 +244,12 @@ public class HelpdeskServlet extends AbstractPwmServlet {
 
 
             returnValues.setActions(actions);
             returnValues.setActions(actions);
         }
         }
+        {
+            final Map<String,Collection<IdentityVerificationMethod>> verificationMethodsMap = new HashMap<>();
+            verificationMethodsMap.put("optional", helpdeskProfile.readOptionalVerificationMethods());
+            verificationMethodsMap.put("required", helpdeskProfile.readRequiredVerificationMethods());
+            returnValues.setVerificationMethods(verificationMethodsMap);
+        }
         final RestResultBean restResultBean = new RestResultBean(returnValues);
         final RestResultBean restResultBean = new RestResultBean(returnValues);
         LOGGER.trace(pwmRequest, "returning clientData: " + JsonUtil.serialize(restResultBean));
         LOGGER.trace(pwmRequest, "returning clientData: " + JsonUtil.serialize(restResultBean));
         pwmRequest.outputJsonResult(restResultBean);
         pwmRequest.outputJsonResult(restResultBean);
@@ -418,6 +433,19 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         }
         }
         LOGGER.trace(pwmRequest, "helpdesk detail view request for user details of " + userIdentity.toString() + " by actor " + actorUserIdentity.toString());
         LOGGER.trace(pwmRequest, "helpdesk detail view request for user details of " + userIdentity.toString() + " by actor " + actorUserIdentity.toString());
 
 
+        final HelpdeskVerificationStateBean verificationStateBean = HelpdeskVerificationStateBean.fromClientString(
+                pwmRequest,
+                pwmRequest.readParameterAsString(HelpdeskVerificationStateBean.PARAMETER_VERIFICATION_STATE_KEY, PwmHttpRequestWrapper.Flag.BypassValidation)
+        );
+
+        if (!checkIfRequiredVerificationPassed(userIdentity, verificationStateBean, helpdeskProfile)) {
+            final String errorMsg = "selected user has not been verified";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,errorMsg);
+            LOGGER.debug(pwmRequest, errorInformation);
+            pwmRequest.respondWithError(errorInformation, false);
+            return;
+        }
+
         final HelpdeskDetailInfoBean helpdeskDetailInfoBean = makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
         final HelpdeskDetailInfoBean helpdeskDetailInfoBean = makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
         pwmRequest.setAttribute(PwmRequest.Attribute.HelpdeskDetail, helpdeskDetailInfoBean);
         pwmRequest.setAttribute(PwmRequest.Attribute.HelpdeskDetail, helpdeskDetailInfoBean);
 
 
@@ -428,6 +456,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         }
         }
 
 
         StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_USER_LOOKUP);
         StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_USER_LOOKUP);
+        pwmRequest.setAttribute(PwmRequest.Attribute.HelpdeskVerificationEnabled, !helpdeskProfile.readOptionalVerificationMethods().isEmpty());
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_DETAIL);
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_DETAIL);
     }
     }
 
 
@@ -485,7 +514,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
 
 
         final RestResultBean restResultBean = new RestResultBean();
         final RestResultBean restResultBean = new RestResultBean();
         final HelpdeskSearchResultsBean outputData = new HelpdeskSearchResultsBean();
         final HelpdeskSearchResultsBean outputData = new HelpdeskSearchResultsBean();
-        outputData.setSearchResults(results.resultsAsJsonOutput(pwmRequest.getPwmApplication()));
+        outputData.setSearchResults(results.resultsAsJsonOutput(pwmRequest.getPwmApplication(),pwmRequest.getUserInfoIfLoggedIn()));
         outputData.setSizeExceeded(sizeExceeded);
         outputData.setSizeExceeded(sizeExceeded);
         restResultBean.setData(outputData);
         restResultBean.setData(outputData);
         pwmRequest.outputJsonResult(restResultBean);
         pwmRequest.outputJsonResult(restResultBean);
@@ -650,11 +679,13 @@ public class HelpdeskServlet extends AbstractPwmServlet {
     )
     )
             throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
             throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
     {
     {
-        final long DELAY_MS = 1000;
         final Date startTime = new Date();
         final Date startTime = new Date();
 
 
-        final Map<String,String> inputRecord = pwmRequest.readBodyAsJsonStringMap();
-        final String userKey = inputRecord.get("userKey");
+        final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonUtil.deserialize(
+                pwmRequest.readRequestBodyAsString(),
+                HelpdeskVerificationRequestBean.class
+        );
+        final String userKey = helpdeskVerificationRequestBean.getUserKey();
         if (userKey == null || userKey.isEmpty()) {
         if (userKey == null || userKey.isEmpty()) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
             pwmRequest.respondWithError(errorInformation, false);
             pwmRequest.respondWithError(errorInformation, false);
@@ -669,7 +700,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
             return;
             return;
         }
         }
 
 
-        final String code = inputRecord.get("code");
+        final String code = helpdeskVerificationRequestBean.getCode();
         final OTPUserRecord otpUserRecord = pwmRequest.getPwmApplication().getOtpService().readOTPUserConfiguration(pwmRequest.getSessionLabel(), userIdentity);
         final OTPUserRecord otpUserRecord = pwmRequest.getPwmApplication().getOtpService().readOTPUserConfiguration(pwmRequest.getSessionLabel(), userIdentity);
         try {
         try {
             final boolean passed = pwmRequest.getPwmApplication().getOtpService().validateToken(
             final boolean passed = pwmRequest.getPwmApplication().getOtpService().validateToken(
@@ -679,9 +710,10 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                     code,
                     code,
                     false
                     false
             );
             );
+
+            final HelpdeskVerificationStateBean verificationStateBean = HelpdeskVerificationStateBean.fromClientString(pwmRequest, helpdeskVerificationRequestBean.getVerificationState());
+
             if (passed) {
             if (passed) {
-                // mark the event log
-                {
                     final PwmSession pwmSession = pwmRequest.getPwmSession();
                     final PwmSession pwmSession = pwmRequest.getPwmSession();
                     final HelpdeskAuditRecord auditRecord = pwmRequest.getPwmApplication().getAuditManager().createHelpdeskAuditRecord(
                     final HelpdeskAuditRecord auditRecord = pwmRequest.getPwmApplication().getAuditManager().createHelpdeskAuditRecord(
                             AuditEvent.HELPDESK_VERIFY_OTP,
                             AuditEvent.HELPDESK_VERIFY_OTP,
@@ -692,18 +724,30 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                             pwmSession.getSessionStateBean().getSrcHostname()
                             pwmSession.getSessionStateBean().getSrcHostname()
                     );
                     );
                     pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
                     pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
-                }
 
 
                 StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_VERIFY_OTP);
                 StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_VERIFY_OTP);
+                verificationStateBean.addRecord(userIdentity, IdentityVerificationMethod.OTP);
+            } else {
+                    final PwmSession pwmSession = pwmRequest.getPwmSession();
+                    final HelpdeskAuditRecord auditRecord = pwmRequest.getPwmApplication().getAuditManager().createHelpdeskAuditRecord(
+                            AuditEvent.HELPDESK_VERIFY_OTP_INCORRECT,
+                            pwmSession.getUserInfoBean().getUserIdentity(),
+                            null,
+                            userIdentity,
+                            pwmSession.getSessionStateBean().getSrcAddress(),
+                            pwmSession.getSessionStateBean().getSrcHostname()
+                    );
+                    pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
             }
             }
 
 
             // add a delay to prevent continuous checks
             // add a delay to prevent continuous checks
-            while (TimeDuration.fromCurrent(startTime).isShorterThan(DELAY_MS)) {
+            final long delayMs = Long.parseLong(pwmRequest.getConfig().readAppProperty(AppProperty.HELPDESK_VERIFICATION_INVALID_DELAY_MS));
+            while (TimeDuration.fromCurrent(startTime).isShorterThan(delayMs)) {
                 Helper.pause(100);
                 Helper.pause(100);
             }
             }
 
 
-            final RestResultBean restResultBean = new RestResultBean();
-            restResultBean.setData(passed);
+            final HelpdeskVerificationResponseBean responseBean = new HelpdeskVerificationResponseBean(passed, verificationStateBean.toClientString(pwmRequest.getPwmApplication()));
+            final RestResultBean restResultBean = new RestResultBean(responseBean);
             pwmRequest.outputJsonResult(restResultBean);
             pwmRequest.outputJsonResult(restResultBean);
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation(), pwmRequest));
             pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation(), pwmRequest));
@@ -733,7 +777,7 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                 }
                 }
             }
             }
             if (tokenSendMethod == MessageSendMethod.CHOICE_SMS_EMAIL) {
             if (tokenSendMethod == MessageSendMethod.CHOICE_SMS_EMAIL) {
-                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT, "unable to determine appropriate send method, missing method parameter indicaton from operator");
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT, "unable to determine appropriate send method, missing method parameter indication from operator");
                 LOGGER.error(pwmRequest,errorInformation);
                 LOGGER.error(pwmRequest,errorInformation);
                 pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation,pwmRequest));
                 pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation,pwmRequest));
                 return;
                 return;
@@ -743,6 +787,12 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         final UserIdentity userIdentity = UserIdentity.fromKey(bodyParams.get("userKey"), pwmRequest.getPwmApplication());
         final UserIdentity userIdentity = UserIdentity.fromKey(bodyParams.get("userKey"), pwmRequest.getPwmApplication());
 
 
         final HelpdeskDetailInfoBean helpdeskDetailInfoBean = makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
         final HelpdeskDetailInfoBean helpdeskDetailInfoBean = makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
+        if (helpdeskDetailInfoBean == null) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "unable to read helpdesk detail data for specified user");
+            LOGGER.error(pwmRequest,errorInformation);
+            pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation,pwmRequest));
+            return;
+        }
         final UserInfoBean userInfoBean = helpdeskDetailInfoBean.getUserInfoBean();
         final UserInfoBean userInfoBean = helpdeskDetailInfoBean.getUserInfoBean();
         final UserDataReader userDataReader = LdapUserDataReader.appProxiedReader(pwmRequest.getPwmApplication(), userIdentity);
         final UserDataReader userDataReader = LdapUserDataReader.appProxiedReader(pwmRequest.getPwmApplication(), userIdentity);
         final MacroMachine macroMachine = new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userInfoBean, null, userDataReader);
         final MacroMachine macroMachine = new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userInfoBean, null, userDataReader);
@@ -816,7 +866,8 @@ public class HelpdeskServlet extends AbstractPwmServlet {
     )
     )
             throws IOException, PwmUnrecoverableException, ServletException
             throws IOException, PwmUnrecoverableException, ServletException
     {
     {
-        final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean =JsonUtil.deserialize(
+        final Date startTime = new Date();
+        final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonUtil.deserialize(
                 pwmRequest.readRequestBodyAsString(),
                 pwmRequest.readRequestBodyAsString(),
                 HelpdeskVerificationRequestBean.class
                 HelpdeskVerificationRequestBean.class
         );
         );
@@ -843,7 +894,8 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         }
         }
 
 
         final boolean passed = tokenData.getToken().equals(token);
         final boolean passed = tokenData.getToken().equals(token);
-        final RestResultBean restResultBean = new RestResultBean(passed);
+
+        final HelpdeskVerificationStateBean verificationStateBean = HelpdeskVerificationStateBean.fromClientString(pwmRequest, helpdeskVerificationRequestBean.getVerificationState());
 
 
         if (passed) {
         if (passed) {
             final PwmSession pwmSession = pwmRequest.getPwmSession();
             final PwmSession pwmSession = pwmRequest.getPwmSession();
@@ -856,8 +908,28 @@ public class HelpdeskServlet extends AbstractPwmServlet {
                     pwmSession.getSessionStateBean().getSrcHostname()
                     pwmSession.getSessionStateBean().getSrcHostname()
             );
             );
             pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
             pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
+            verificationStateBean.addRecord(userIdentity, IdentityVerificationMethod.TOKEN);
+        } else {
+            final PwmSession pwmSession = pwmRequest.getPwmSession();
+            final HelpdeskAuditRecord auditRecord = pwmRequest.getPwmApplication().getAuditManager().createHelpdeskAuditRecord(
+                    AuditEvent.HELPDESK_VERIFY_TOKEN_INCORRECT,
+                    pwmSession.getUserInfoBean().getUserIdentity(),
+                    null,
+                    userIdentity,
+                    pwmSession.getSessionStateBean().getSrcAddress(),
+                    pwmSession.getSessionStateBean().getSrcHostname()
+            );
+            pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
         }
         }
 
 
+        // add a delay to prevent continuous checks
+        final long delayMs = Long.parseLong(pwmRequest.getConfig().readAppProperty(AppProperty.HELPDESK_VERIFICATION_INVALID_DELAY_MS));
+        while (TimeDuration.fromCurrent(startTime).isShorterThan(delayMs)) {
+            Helper.pause(100);
+        }
+
+        final HelpdeskVerificationResponseBean responseBean = new HelpdeskVerificationResponseBean(passed, verificationStateBean.toClientString(pwmRequest.getPwmApplication()));
+        final RestResultBean restResultBean = new RestResultBean(responseBean);
         pwmRequest.outputJsonResult(restResultBean);
         pwmRequest.outputJsonResult(restResultBean);
     }
     }
 
 
@@ -972,4 +1044,56 @@ public class HelpdeskServlet extends AbstractPwmServlet {
         }
         }
     }
     }
 
 
+    private void restCheckVerification(final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile)
+            throws IOException, PwmUnrecoverableException, ServletException {
+        final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
+
+        final String userKey = bodyMap.get("userKey");
+        if (userKey == null || userKey.length() < 1) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
+            pwmRequest.setResponseError(errorInformation);
+            pwmRequest.respondWithError(errorInformation, false);
+            return;
+        }
+
+        final UserIdentity userIdentity = UserIdentity.fromObfuscatedKey(userKey, pwmRequest.getPwmApplication());
+        checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+
+        final String rawVerificationStr = bodyMap.get(HelpdeskVerificationStateBean.PARAMETER_VERIFICATION_STATE_KEY);
+        final HelpdeskVerificationStateBean state = HelpdeskVerificationStateBean.fromClientString(pwmRequest, rawVerificationStr);
+        final boolean passed = checkIfRequiredVerificationPassed(userIdentity, state, helpdeskProfile);
+        final HashMap<String,Object> results = new HashMap<>();
+        results.put("passed",passed);
+        RestResultBean restResultBean = new RestResultBean(results);
+        pwmRequest.outputJsonResult(restResultBean);
+    }
+
+    private boolean checkIfRequiredVerificationPassed(final UserIdentity userIdentity, final HelpdeskVerificationStateBean verificationStateBean, final HelpdeskProfile helpdeskProfile) {
+        final Collection<IdentityVerificationMethod> requiredMethods = helpdeskProfile.readRequiredVerificationMethods();
+        if (requiredMethods == null || requiredMethods.isEmpty()) {
+            return true;
+        }
+        for (final IdentityVerificationMethod method : requiredMethods) {
+            if (!verificationStateBean.hasRecord(userIdentity, method)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void restShowVerifications(final PwmRequest pwmRequest)
+            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
+    {
+        final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
+        final String rawVerificationStr = bodyMap.get(HelpdeskVerificationStateBean.PARAMETER_VERIFICATION_STATE_KEY);
+        final HelpdeskVerificationStateBean state = HelpdeskVerificationStateBean.fromClientString(pwmRequest, rawVerificationStr);
+        final HashMap<String,Object> results = new HashMap<>();
+        try {
+            results.put("records",state.asViewableValidationRecords(pwmRequest.getPwmApplication(), pwmRequest.getLocale()));
+        } catch (ChaiOperationException e) {
+            throw PwmUnrecoverableException.fromChaiException(e);
+        }
+        RestResultBean restResultBean = new RestResultBean(results);
+        pwmRequest.outputJsonResult(restResultBean);
+    }
 }
 }

+ 10 - 1
src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationRequestBean.java

@@ -30,7 +30,8 @@ public class HelpdeskVerificationRequestBean implements Serializable {
     private String destination;
     private String destination;
     private String userKey;
     private String userKey;
     private String code;
     private String code;
-    private String tokenData;
+    private String tokenData; // encrypted during transport
+    private String verificationState;
 
 
     public String getDestination() {
     public String getDestination() {
         return destination;
         return destination;
@@ -64,6 +65,14 @@ public class HelpdeskVerificationRequestBean implements Serializable {
         this.tokenData = tokenData;
         this.tokenData = tokenData;
     }
     }
 
 
+    public String getVerificationState() {
+        return verificationState;
+    }
+
+    public void setVerificationState(String verificationState) {
+        this.verificationState = verificationState;
+    }
+
     static class TokenData implements Serializable {
     static class TokenData implements Serializable {
         private String token;
         private String token;
         private Date issueDate;
         private Date issueDate;

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

@@ -26,9 +26,9 @@ import java.io.Serializable;
 
 
 public class HelpdeskVerificationResponseBean implements Serializable {
 public class HelpdeskVerificationResponseBean implements Serializable {
     private boolean passed;
     private boolean passed;
-    private HelpdeskVerificationStateBean verificationState;
+    private String verificationState;
 
 
-    public HelpdeskVerificationResponseBean(boolean passed, HelpdeskVerificationStateBean verificationState) {
+    public HelpdeskVerificationResponseBean(boolean passed, String verificationState) {
         this.passed = passed;
         this.passed = passed;
         this.verificationState = verificationState;
         this.verificationState = verificationState;
     }
     }
@@ -37,7 +37,7 @@ public class HelpdeskVerificationResponseBean implements Serializable {
         return passed;
         return passed;
     }
     }
 
 
-    public HelpdeskVerificationStateBean getVerificationState() {
+    public String getVerificationState() {
         return verificationState;
         return verificationState;
     }
     }
 }
 }

+ 140 - 9
src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationStateBean.java

@@ -22,19 +22,34 @@
 
 
 package password.pwm.http.servlet.helpdesk;
 package password.pwm.http.servlet.helpdesk;
 
 
+import com.novell.ldapchai.exception.ChaiOperationException;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
+import password.pwm.config.option.IdentityVerificationMethod;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.ldap.LdapOperationsHelper;
+import password.pwm.util.JsonUtil;
+import password.pwm.util.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 
-public class HelpdeskVerificationStateBean implements Serializable {
-    private UserIdentity actor;
-    private List<HelpdeskValidationRecord> records;
+class HelpdeskVerificationStateBean implements Serializable {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(HelpdeskVerificationStateBean.class);
+    private final UserIdentity actor;
+    private final List<HelpdeskValidationRecord> records = new ArrayList<>();
 
 
-    public HelpdeskVerificationStateBean(UserIdentity actor, List<HelpdeskValidationRecord> records) {
+    public static final String PARAMETER_VERIFICATION_STATE_KEY = "verificationState";
+
+    private transient TimeDuration maximumAge;
+
+    private HelpdeskVerificationStateBean(final UserIdentity actor) {
         this.actor = actor;
         this.actor = actor;
-        this.records = records;
     }
     }
 
 
     public UserIdentity getActor() {
     public UserIdentity getActor() {
@@ -45,13 +60,92 @@ public class HelpdeskVerificationStateBean implements Serializable {
         return records;
         return records;
     }
     }
 
 
-    static class HelpdeskValidationRecord {
+    public void addRecord(final UserIdentity identity, final IdentityVerificationMethod method) {
+        purgeOldRecords();
+
+        final HelpdeskValidationRecord record = getRecord(identity, method);
+        if (record != null) {
+            records.remove(record);
+        }
+        records.add(new HelpdeskValidationRecord(new Date(), identity, method));
+    }
+
+    public boolean hasRecord(final UserIdentity identity, final IdentityVerificationMethod method) {
+        purgeOldRecords();
+        return getRecord(identity,method) != null;
+    }
+
+    private HelpdeskValidationRecord getRecord(final UserIdentity identity, final IdentityVerificationMethod method) {
+        for (final HelpdeskValidationRecord record : records) {
+            if (record.getIdentity().equals(identity) && (method == null || record.getMethod() == method)) {
+                return record;
+            }
+        }
+        return null;
+    }
+
+
+    void purgeOldRecords() {
+        for (Iterator<HelpdeskValidationRecord> iterator = records.iterator(); iterator.hasNext(); ) {
+            final HelpdeskValidationRecord record = iterator.next();
+            final Date timestamp = record.getTimestamp();
+            final TimeDuration age = TimeDuration.fromCurrent(timestamp);
+            if (age.isLongerThan(maximumAge)) {
+                iterator.remove();
+            }
+        }
+    }
+
+    List<ViewableValidationRecord> asViewableValidationRecords(final PwmApplication pwmApplication, final Locale locale) throws ChaiOperationException, ChaiUnavailableException, PwmUnrecoverableException {
+        final Map<Date,ViewableValidationRecord> returnRecords = new TreeMap<>();
+        for (final HelpdeskValidationRecord record : records) {
+            final String username = LdapOperationsHelper.readLdapUsernameValue(pwmApplication, record.getIdentity());
+            final String profile = pwmApplication.getConfig().getLdapProfiles().get(record.getIdentity().getLdapProfileID()).getDisplayName(locale);
+            final String method = record.getMethod().getLabel(pwmApplication.getConfig(), locale);
+            returnRecords.put(record.getTimestamp(), new ViewableValidationRecord(record.getTimestamp(), profile, username, method));
+        }
+        return Collections.unmodifiableList(new ArrayList<>(returnRecords.values()));
+    }
+
+    static class ViewableValidationRecord implements Serializable {
+        private Date timestamp;
+        private String profile;
+        private String username;
+        private String method;
+
+        public ViewableValidationRecord(Date timestamp, String profile, String username, String method) {
+            this.timestamp = timestamp;
+            this.profile = profile;
+            this.username = username;
+            this.method = method;
+        }
+
+        public Date getTimestamp() {
+            return timestamp;
+        }
+
+        public String getProfile() {
+            return profile;
+        }
+
+        public String getUsername() {
+            return username;
+        }
+
+        public String getMethod() {
+            return method;
+        }
+    }
+
+    static class HelpdeskValidationRecord implements Serializable {
         private Date timestamp;
         private Date timestamp;
         private UserIdentity identity;
         private UserIdentity identity;
+        private IdentityVerificationMethod method;
 
 
-        public HelpdeskValidationRecord(Date timestamp, UserIdentity identity) {
+        public HelpdeskValidationRecord(Date timestamp, UserIdentity identity, IdentityVerificationMethod method) {
             this.timestamp = timestamp;
             this.timestamp = timestamp;
             this.identity = identity;
             this.identity = identity;
+            this.method = method;
         }
         }
 
 
         public Date getTimestamp() {
         public Date getTimestamp() {
@@ -61,5 +155,42 @@ public class HelpdeskVerificationStateBean implements Serializable {
         public UserIdentity getIdentity() {
         public UserIdentity getIdentity() {
             return identity;
             return identity;
         }
         }
+
+        public IdentityVerificationMethod getMethod() {
+            return method;
+        }
+    }
+
+    String toClientString(final PwmApplication pwmApplication) throws PwmUnrecoverableException {
+        return pwmApplication.getSecureService().encryptObjectToString(this);
+    }
+
+    static HelpdeskVerificationStateBean fromClientString(
+            final PwmRequest pwmRequest,
+            final String rawValue
+    )
+            throws PwmUnrecoverableException
+    {
+        final int maxAgeSeconds = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.HELPDESK_VERIFICATION_TIMEOUT_SECONDS));
+        final TimeDuration maxAge = new TimeDuration(maxAgeSeconds, TimeUnit.SECONDS);
+        final UserIdentity actor = pwmRequest.getUserInfoIfLoggedIn();
+
+        HelpdeskVerificationStateBean state = null;
+        if (rawValue != null && !rawValue.isEmpty()) {
+            state = pwmRequest.getPwmApplication().getSecureService().decryptObject(rawValue, HelpdeskVerificationStateBean.class);
+            if (!state.getActor().equals(actor)) {
+                state = null;
+            }
+        }
+
+        state = state != null ? state : new HelpdeskVerificationStateBean(actor);
+        state.maximumAge = maxAge;
+        state.purgeOldRecords();
+
+        LOGGER.debug(pwmRequest, "read current state: " + JsonUtil.serialize(state));
+
+        return state;
     }
     }
 }
 }
+
+

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

@@ -273,7 +273,7 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
                 searchDuration.asCompactString() + " not using cache, size=" + results.getResults().size());
                 searchDuration.asCompactString() + " not using cache, size=" + results.getResults().size());
 
 
         final SearchResultBean searchResultBean = new SearchResultBean();
         final SearchResultBean searchResultBean = new SearchResultBean();
-        searchResultBean.setSearchResults(new ArrayList<>(results.resultsAsJsonOutput(pwmRequest.getPwmApplication())));
+        searchResultBean.setSearchResults(new ArrayList<>(results.resultsAsJsonOutput(pwmRequest.getPwmApplication(),null)));
         searchResultBean.setSizeExceeded(sizeExceeded);
         searchResultBean.setSizeExceeded(sizeExceeded);
         final String aboutMessage = LocaleHelper.getLocalizedMessage(
         final String aboutMessage = LocaleHelper.getLocalizedMessage(
                 pwmRequest.getLocale(),
                 pwmRequest.getLocale(),

+ 2 - 0
src/main/java/password/pwm/i18n/Display.java

@@ -56,6 +56,7 @@ public enum Display implements PwmDisplayBundle {
     Button_Logout,
     Button_Logout,
     Button_More,
     Button_More,
     Button_OrgChart,
     Button_OrgChart,
+    Button_OTP,
     Button_RecoverPassword,
     Button_RecoverPassword,
     Button_Reset,
     Button_Reset,
     Button_Search,
     Button_Search,
@@ -295,6 +296,7 @@ public enum Display implements PwmDisplayBundle {
     Value_True,
     Value_True,
     Value_NotApplicable,
     Value_NotApplicable,
     Value_Default,
     Value_Default,
+    Placeholder_Search,
     
     
     ;
     ;
 
 

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

@@ -84,7 +84,9 @@ public enum Message implements PwmDisplayBundle {
     EventLog_HelpdeskDeleteUser(null),
     EventLog_HelpdeskDeleteUser(null),
     EventLog_HelpdeskViewDetail(null),
     EventLog_HelpdeskViewDetail(null),
     EventLog_HelpdeskVerifyOtp(null),
     EventLog_HelpdeskVerifyOtp(null),
+    EventLog_HelpdeskVerifyOtpIncorrect(null),
     EventLog_HelpdeskVerifyToken(null),
     EventLog_HelpdeskVerifyToken(null),
+    EventLog_HelpdeskVerifyTokenIncorrect(null),
 
 
     Requirement_MinLengthPlural(null),
     Requirement_MinLengthPlural(null),
     Requirement_MinLength(Requirement_MinLengthPlural),
     Requirement_MinLength(Requirement_MinLengthPlural),

+ 10 - 8
src/main/java/password/pwm/ldap/UserSearchEngine.java

@@ -617,20 +617,22 @@ public class UserSearchEngine {
             return sizeExceeded;
             return sizeExceeded;
         }
         }
 
 
-        public List<Map<String,Object>> resultsAsJsonOutput(final PwmApplication pwmApplication)
+        public List<Map<String,Object>> resultsAsJsonOutput(final PwmApplication pwmApplication, final UserIdentity ignoreUser)
                 throws PwmUnrecoverableException
                 throws PwmUnrecoverableException
         {
         {
             final List<Map<String,Object>> outputList = new ArrayList<>();
             final List<Map<String,Object>> outputList = new ArrayList<>();
             int idCounter = 0;
             int idCounter = 0;
             for (final UserIdentity userIdentity : this.getResults().keySet()) {
             for (final UserIdentity userIdentity : this.getResults().keySet()) {
-                final Map<String,Object> rowMap = new LinkedHashMap<>();
-                for (final String attribute : this.getHeaderAttributeMap().keySet()) {
-                    rowMap.put(attribute,this.getResults().get(userIdentity).get(attribute));
+                if (ignoreUser == null || !ignoreUser.equals(userIdentity)) {
+                    final Map<String, Object> rowMap = new LinkedHashMap<>();
+                    for (final String attribute : this.getHeaderAttributeMap().keySet()) {
+                        rowMap.put(attribute, this.getResults().get(userIdentity).get(attribute));
+                    }
+                    rowMap.put("userKey", userIdentity.toObfuscatedKey(pwmApplication));
+                    rowMap.put("id", idCounter);
+                    outputList.add(rowMap);
+                    idCounter++;
                 }
                 }
-                rowMap.put("userKey",userIdentity.toObfuscatedKey(pwmApplication));
-                rowMap.put("id",idCounter);
-                outputList.add(rowMap);
-                idCounter++;
             }
             }
             return outputList;
             return outputList;
         }
         }

+ 2 - 0
src/main/java/password/pwm/svc/event/AuditEvent.java

@@ -64,7 +64,9 @@ public enum AuditEvent {
     HELPDESK_DELETE_USER(Message.EventLog_HelpdeskDeleteUser, Type.HELPDESK),
     HELPDESK_DELETE_USER(Message.EventLog_HelpdeskDeleteUser, Type.HELPDESK),
     HELPDESK_VIEW_DETAIL(Message.EventLog_HelpdeskViewDetail, Type.HELPDESK),
     HELPDESK_VIEW_DETAIL(Message.EventLog_HelpdeskViewDetail, Type.HELPDESK),
     HELPDESK_VERIFY_OTP(Message.EventLog_HelpdeskVerifyOtp, Type.HELPDESK),
     HELPDESK_VERIFY_OTP(Message.EventLog_HelpdeskVerifyOtp, Type.HELPDESK),
+    HELPDESK_VERIFY_OTP_INCORRECT(Message.EventLog_HelpdeskVerifyOtpIncorrect, Type.HELPDESK),
     HELPDESK_VERIFY_TOKEN(Message.EventLog_HelpdeskVerifyToken, Type.HELPDESK),
     HELPDESK_VERIFY_TOKEN(Message.EventLog_HelpdeskVerifyToken, Type.HELPDESK),
+    HELPDESK_VERIFY_TOKEN_INCORRECT(Message.EventLog_HelpdeskVerifyOtpIncorrect, Type.HELPDESK),
 
 
 
 
     ;
     ;

+ 8 - 10
src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java

@@ -132,7 +132,7 @@ public class NMASCrOperator implements CrOperator {
     }
     }
 
 
     public void close() {
     public void close() {
-        final List<NMASSessionThread> threads = new ArrayList(sessionMonitorThreads);
+        final List<NMASSessionThread> threads = new ArrayList<>(sessionMonitorThreads);
         for (final NMASSessionThread thread : threads) {
         for (final NMASSessionThread thread : threads) {
             LOGGER.debug("killing thread due to NMASCrOperator service closing: " + thread.toDebugString());
             LOGGER.debug("killing thread due to NMASCrOperator service closing: " + thread.toDebugString());
             thread.abort();
             thread.abort();
@@ -146,7 +146,6 @@ public class NMASCrOperator implements CrOperator {
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final String userDN = theUser.getEntryDN();
         pwmApplication.getIntruderManager().convenience().checkUserIdentity(userIdentity);
         pwmApplication.getIntruderManager().convenience().checkUserIdentity(userIdentity);
 
 
         try {
         try {
@@ -657,7 +656,7 @@ public class NMASCrOperator implements CrOperator {
                             this.loginDN,
                             this.loginDN,
                             "dn:" + this.loginDN,
                             "dn:" + this.loginDN,
                             new String[] { "NMAS_LOGIN" },
                             new String[] { "NMAS_LOGIN" },
-                            new HashMap(CR_OPTIONS_MAP),
+                            new HashMap<>(CR_OPTIONS_MAP),
                             this.callbackHandler
                             this.callbackHandler
                     );
                     );
                 } catch (NullPointerException e) {
                 } catch (NullPointerException e) {
@@ -726,8 +725,8 @@ public class NMASCrOperator implements CrOperator {
     private class ThreadWatchdogTask extends TimerTask {
     private class ThreadWatchdogTask extends TimerTask {
         @Override
         @Override
         public void run() {
         public void run() {
-            //logThreadInfo();
-            final List<NMASSessionThread> threads = new ArrayList(sessionMonitorThreads);
+            logThreadInfo();
+            final List<NMASSessionThread> threads = new ArrayList<>(sessionMonitorThreads);
             for (final NMASSessionThread thread : threads) {
             for (final NMASSessionThread thread : threads) {
                 final TimeDuration idleTime = TimeDuration.fromCurrent(thread.getLastActivityTimestamp());
                 final TimeDuration idleTime = TimeDuration.fromCurrent(thread.getLastActivityTimestamp());
                 if (idleTime.isLongerThan(maxThreadIdleTime)) {
                 if (idleTime.isLongerThan(maxThreadIdleTime)) {
@@ -737,9 +736,9 @@ public class NMASCrOperator implements CrOperator {
             }
             }
         }
         }
 
 
-        /*
+
         private void logThreadInfo() {
         private void logThreadInfo() {
-            final List<NMASSessionThread> threads = new ArrayList(sessionMonitorThreads);
+            final List<NMASSessionThread> threads = new ArrayList<>(sessionMonitorThreads);
             final StringBuilder threadDebugInfo = new StringBuilder();
             final StringBuilder threadDebugInfo = new StringBuilder();
             threadDebugInfo.append("NMASCrOperator watchdog timer, activeCount=").append(threads.size());
             threadDebugInfo.append("NMASCrOperator watchdog timer, activeCount=").append(threads.size());
             threadDebugInfo.append(", maxIdleThreadTime=").append(maxThreadIdleTime.asCompactString());
             threadDebugInfo.append(", maxIdleThreadTime=").append(maxThreadIdleTime.asCompactString());
@@ -748,8 +747,7 @@ public class NMASCrOperator implements CrOperator {
             }
             }
             LOGGER.trace(threadDebugInfo.toString());
             LOGGER.trace(threadDebugInfo.toString());
         }
         }
-        */
-    }
-
 
 
+    }
 }
 }
+

+ 5 - 5
src/main/java/password/pwm/ws/client/rest/naaf/NAAFLoginSequence.java

@@ -22,7 +22,7 @@
 
 
 package password.pwm.ws.client.rest.naaf;
 package password.pwm.ws.client.rest.naaf;
 
 
-import password.pwm.RecoveryVerificationMethod;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
@@ -178,14 +178,14 @@ public class NAAFLoginSequence {
         }
         }
     }
     }
 
 
-    public RecoveryVerificationMethod.VerificationState status() {
+    public VerificationMethodSystem.VerificationState status() {
         if (lastError != null) {
         if (lastError != null) {
-            return RecoveryVerificationMethod.VerificationState.FAILED;
+            return VerificationMethodSystem.VerificationState.FAILED;
         }
         }
         if (completedMethods.containsAll(requiredMethods)) {
         if (completedMethods.containsAll(requiredMethods)) {
-            return RecoveryVerificationMethod.VerificationState.COMPLETE;
+            return VerificationMethodSystem.VerificationState.COMPLETE;
         }
         }
-        return RecoveryVerificationMethod.VerificationState.INPROGRESS;
+        return VerificationMethodSystem.VerificationState.INPROGRESS;
     }
     }
 
 
 
 

+ 2 - 2
src/main/java/password/pwm/ws/client/rest/naaf/PwmNAAFVerificationMethod.java

@@ -24,7 +24,7 @@ package password.pwm.ws.client.rest.naaf;
 
 
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
-import password.pwm.RecoveryVerificationMethod;
+import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
@@ -37,7 +37,7 @@ import password.pwm.util.macro.MacroMachine;
 
 
 import java.util.*;
 import java.util.*;
 
 
-public class PwmNAAFVerificationMethod implements RecoveryVerificationMethod {
+public class PwmNAAFVerificationMethod implements VerificationMethodSystem {
     private PwmApplication pwmApplication;
     private PwmApplication pwmApplication;
     private NAAFLoginSequence naafLoginSequence;
     private NAAFLoginSequence naafLoginSequence;
     private Locale locale;
     private Locale locale;

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

@@ -65,8 +65,10 @@ health.certificate.warnSeconds=2592000
 health.ldap.cautionDurationMS=10800000
 health.ldap.cautionDurationMS=10800000
 health.java.maxThreads=1000
 health.java.maxThreads=1000
 health.java.minHeapBytes=67108864
 health.java.minHeapBytes=67108864
-helpdesk.token.maxAgeSeconds=120
+helpdesk.token.maxAgeSeconds=300
 helpdesk.token.value=@RandomChar:6:0123456789@
 helpdesk.token.value=@RandomChar:6:0123456789@
+helpdesk.verification.invalid.delayMs=2000
+helpdesk.verification.timeoutSeconds=3600
 http.resources.maxCacheItems=500
 http.resources.maxCacheItems=500
 http.resources.maxCacheBytes=500000
 http.resources.maxCacheBytes=500000
 http.resources.expirationSeconds=30240000
 http.resources.expirationSeconds=30240000

+ 11 - 10
src/main/resources/password/pwm/config/PwmSetting.xml

@@ -1469,7 +1469,9 @@
             <value>HELPDESK_VIEW_DETAIL</value>
             <value>HELPDESK_VIEW_DETAIL</value>
             <value>HELPDESK_ACTION</value>
             <value>HELPDESK_ACTION</value>
             <value>HELPDESK_VERIFY_OTP</value>
             <value>HELPDESK_VERIFY_OTP</value>
+            <value>HELPDESK_VERIFY_OTP_INCORRECT</value>
             <value>HELPDESK_VERIFY_TOKEN</value>
             <value>HELPDESK_VERIFY_TOKEN</value>
+            <value>HELPDESK_VERIFY_TOKEN_INCORRECT</value>
         </default>
         </default>
         <options>
         <options>
             <option value="AUTHENTICATE">Authenticate</option>
             <option value="AUTHENTICATE">Authenticate</option>
@@ -1494,7 +1496,9 @@
             <option value="HELPDESK_VIEW_DETAIL">Helpdesk View Detail</option>
             <option value="HELPDESK_VIEW_DETAIL">Helpdesk View Detail</option>
             <option value="HELPDESK_ACTION">Helpdesk Action</option>
             <option value="HELPDESK_ACTION">Helpdesk Action</option>
             <option value="HELPDESK_VERIFY_OTP">Helpdesk Verify OTP</option>
             <option value="HELPDESK_VERIFY_OTP">Helpdesk Verify OTP</option>
+            <option value="HELPDESK_VERIFY_OTP_INCORRECT">Helpdesk Incorrect Verify OTP</option>
             <option value="HELPDESK_VERIFY_TOKEN">Helpdesk Verify Token</option>
             <option value="HELPDESK_VERIFY_TOKEN">Helpdesk Verify Token</option>
+            <option value="HELPDESK_VERIFY_TOKEN_INCORRECT">Helpdesk Incorrect Verify Token</option>
         </options>
         </options>
     </setting>
     </setting>
     <setting hidden="false" key="audit.syslog.servers" level="1" required="false">
     <setting hidden="false" key="audit.syslog.servers" level="1" required="false">
@@ -3144,25 +3148,17 @@
             <option value="NONE">None - Token verification will not be available</option>
             <option value="NONE">None - Token verification will not be available</option>
             <option value="EMAILONLY">Email Only - Send to email address</option>
             <option value="EMAILONLY">Email Only - Send to email address</option>
             <option value="SMSONLY">SMS Only - Send via SMS</option>
             <option value="SMSONLY">SMS Only - Send via SMS</option>
-            <option value="BOTH">Both - Send token to both email and SMS</option>
-            <option value="EMAILFIRST">Email First - Try to send token via email; if no email address is available, send via SMS</option>
-            <option value="SMSFIRST">SMS First - Try to send token via SMS; if no SMS number is available, send via email</option>
             <option value="CHOICE_SMS_EMAIL">Operator Choice - If both SMS and email address is available, helpdesk operator decides</option>
             <option value="CHOICE_SMS_EMAIL">Operator Choice - If both SMS and email address is available, helpdesk operator decides</option>
         </options>
         </options>
     </setting>
     </setting>
-    <setting hidden="false" key="helpdesk.otp.verify" level="1">
-        <default>
-            <value>false</value>
-        </default>
-    </setting>
     <setting hidden="false" key="helpdesk.setPassword.maskValue" level="1">
     <setting hidden="false" key="helpdesk.setPassword.maskValue" level="1">
         <default>
         <default>
             <value>false</value>
             <value>false</value>
         </default>
         </default>
     </setting>
     </setting>
-    <setting hidden="true" key="helpdesk.verificationMethods" level="1">
+    <setting hidden="false" key="helpdesk.verificationMethods" level="1">
         <default>
         <default>
-            <value>{"methodSettings":{"CHALLENGE_RESPONSES":{"enabledState":"required"}},"minOptionalRequired":0}</value>
+            <value>{"methodSettings":{},"minOptionalRequired":0}</value>
         </default>
         </default>
         <options>
         <options>
             <!--<option value="ATTRIBUTES">LDAP Attributes</option>-->
             <!--<option value="ATTRIBUTES">LDAP Attributes</option>-->
@@ -3471,6 +3467,11 @@
             <value>true</value>
             <value>true</value>
         </default>
         </default>
     </setting>
     </setting>
+    <setting hidden="true" key="helpdesk.otp.verify" level="1">
+        <default>
+            <value>false</value>
+        </default>
+    </setting>
     <category hidden="false" key="TEMPLATES">
     <category hidden="false" key="TEMPLATES">
     </category>
     </category>
     <category hidden="false" key="NOTES">
     <category hidden="false" key="NOTES">

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

@@ -45,6 +45,7 @@ Button_Login=Sign in
 Button_Logout=Sign out
 Button_Logout=Sign out
 Button_More=More
 Button_More=More
 Button_OrgChart=Organizational Chart
 Button_OrgChart=Organizational Chart
+Button_OTP=OTP
 Button_RecoverPassword=Check Answers
 Button_RecoverPassword=Check Answers
 Button_Reset=Clear
 Button_Reset=Clear
 Button_Search=Search
 Button_Search=Search
@@ -294,3 +295,4 @@ Value_False=False
 Value_True=True
 Value_True=True
 Value_NotApplicable=n/a
 Value_NotApplicable=n/a
 Value_Default=Default
 Value_Default=Default
+Placeholder_Search=Search

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

@@ -40,7 +40,9 @@ EventLog_HelpdeskUnlockPassword=Helpdesk Unlock Password
 EventLog_HelpdeskDeleteUser=Helpdesk Delete User
 EventLog_HelpdeskDeleteUser=Helpdesk Delete User
 EventLog_HelpdeskViewDetail=Helpdesk View Detail
 EventLog_HelpdeskViewDetail=Helpdesk View Detail
 EventLog_HelpdeskVerifyOtp=Helpdesk Verify OTP
 EventLog_HelpdeskVerifyOtp=Helpdesk Verify OTP
+EventLog_HelpdeskVerifyOtpIncorrect=Helpdesk Incorrect OTP Verify
 EventLog_HelpdeskVerifyToken=Helpdesk Verify Token
 EventLog_HelpdeskVerifyToken=Helpdesk Verify Token
+EventLog_HelpdeskVerifyTokenIncorrect=Helpdesk Incorrect Token Verify
 EventLog_IntruderUser=Intruder User Lockout
 EventLog_IntruderUser=Intruder User Lockout
 EventLog_TokenIssued=Token Issued
 EventLog_TokenIssued=Token Issued
 EventLog_TokenClaimed=Token Claimed
 EventLog_TokenClaimed=Token Claimed

+ 1 - 1
src/main/webapp/WEB-INF/jsp/configeditor.jsp

@@ -78,7 +78,7 @@
                         <span id="settingSearchIcon" class="pwm-icon pwm-icon-search" title="<pwm:display key="Tooltip_IconSettingsSearch" bundle="Config"/>"></span>
                         <span id="settingSearchIcon" class="pwm-icon pwm-icon-search" title="<pwm:display key="Tooltip_IconSettingsSearch" bundle="Config"/>"></span>
                     </td>
                     </td>
                     <td>
                     <td>
-                        <input type="search" id="homeSettingSearch" name="homeSettingSearch" class="inputfield" <pwm:autofocus/>/>
+                        <input placeholder="<pwm:display key="Placeholder_Search"/>" type="search" id="homeSettingSearch" name="homeSettingSearch" class="inputfield" <pwm:autofocus/>/>
                     </td>
                     </td>
                     <td>
                     <td>
                         <div style="margin-top:5px; width:20px; max-width: 20px;">
                         <div style="margin-top:5px; width:20px; max-width: 20px;">

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

@@ -1,4 +1,4 @@
-<%@ page import="password.pwm.config.option.RecoveryVerificationMethods" %>
+<%@ page import="password.pwm.config.option.IdentityVerificationMethod" %>
 <%@ page import="java.util.HashSet" %>
 <%@ page import="java.util.HashSet" %>
 <%@ page import="java.util.Set" %>
 <%@ page import="java.util.Set" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
@@ -29,7 +29,7 @@
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%
 <%
     final PwmRequest pwmRequest = PwmRequest.forRequest(request, response);
     final PwmRequest pwmRequest = PwmRequest.forRequest(request, response);
-    final Set<RecoveryVerificationMethods> methods = new HashSet<RecoveryVerificationMethods>((Set<RecoveryVerificationMethods>) JspUtility.getAttribute(pageContext, PwmRequest.Attribute.AvailableAuthMethods));
+    final Set<IdentityVerificationMethod> methods = new HashSet<IdentityVerificationMethod>((Set<IdentityVerificationMethod>) JspUtility.getAttribute(pageContext, PwmRequest.Attribute.AvailableAuthMethods));
 %>
 %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
@@ -47,7 +47,7 @@
             <colgroup>
             <colgroup>
 
 
             </colgroup>
             </colgroup>
-            <% for (RecoveryVerificationMethods method : methods) { %>
+            <% for (IdentityVerificationMethod method : methods) { %>
             <% if (method.isUserSelectable()) { %>
             <% if (method.isUserSelectable()) { %>
             <tr>
             <tr>
                 <td>
                 <td>

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

@@ -22,7 +22,7 @@
 
 
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
-<%@ page import="password.pwm.RecoveryVerificationMethod" %>
+<%@ page import="password.pwm.VerificationMethodSystem" %>
 <%@ page import="java.util.List" %>
 <%@ page import="java.util.List" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
@@ -35,14 +35,14 @@
     </jsp:include>
     </jsp:include>
     <div id="centerbody">
     <div id="centerbody">
         <%
         <%
-            final List<RecoveryVerificationMethod.UserPrompt> prompts = (List<RecoveryVerificationMethod.UserPrompt>)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordPrompts);
+            final List<VerificationMethodSystem.UserPrompt> prompts = (List<VerificationMethodSystem.UserPrompt>)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordPrompts);
             final String instructions = (String)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordInstructions);
             final String instructions = (String)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordInstructions);
         %>
         %>
         <p><%=instructions%></p>
         <p><%=instructions%></p>
         <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form" autocomplete="off">
         <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" %>
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <br/>
             <br/>
-            <% for (final RecoveryVerificationMethod.UserPrompt userPrompt : prompts) { %>
+            <% for (final VerificationMethodSystem.UserPrompt userPrompt : prompts) { %>
             <div class="formFieldLabel">
             <div class="formFieldLabel">
                 <%= userPrompt.getDisplayPrompt() %>
                 <%= userPrompt.getDisplayPrompt() %>
             </div>
             </div>

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

@@ -22,7 +22,7 @@
 
 
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
-<%@ page import="password.pwm.RecoveryVerificationMethod" %>
+<%@ page import="password.pwm.VerificationMethodSystem" %>
 <%@ page import="java.util.List" %>
 <%@ page import="java.util.List" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
@@ -35,14 +35,14 @@
     </jsp:include>
     </jsp:include>
     <div id="centerbody">
     <div id="centerbody">
         <%
         <%
-            final List<RecoveryVerificationMethod.UserPrompt> prompts = (List<RecoveryVerificationMethod.UserPrompt>)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordPrompts);
+            final List<VerificationMethodSystem.UserPrompt> prompts = (List<VerificationMethodSystem.UserPrompt>)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordPrompts);
             final String instructions = (String)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordInstructions);
             final String instructions = (String)JspUtility.getAttribute(pageContext, PwmRequest.Attribute.ForgottenPasswordInstructions);
         %>
         %>
         <p><%=instructions%></p>
         <p><%=instructions%></p>
         <form action="<pwm:current-url/>" method="post" enctype="application/x-www-form-urlencoded" name="search" class="pwm-form" autocomplete="off">
         <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" %>
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <br/>
             <br/>
-            <% for (final RecoveryVerificationMethod.UserPrompt userPrompt : prompts) { %>
+            <% for (final VerificationMethodSystem.UserPrompt userPrompt : prompts) { %>
             <div class="formFieldLabel">
             <div class="formFieldLabel">
                 <%= userPrompt.getDisplayPrompt() %>
                 <%= userPrompt.getDisplayPrompt() %>
             </div>
             </div>

+ 2 - 8
src/main/webapp/WEB-INF/jsp/helpdesk-detail.jsp

@@ -579,16 +579,10 @@
                         </button>
                         </button>
                         <% } %>
                         <% } %>
                         <% } %>
                         <% } %>
-                        <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE_OTP_VERIFY)) { %>
-                        <button id="helpdesk_verifyOtpButton" <%=hasOtp?"":" disabled=\"true\""%>class="helpdesk-detail-btn btn">
-                            <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-mobile-phone"></span></pwm:if>
-                            Verify OTP
-                        </button>
-                        <% } %>
-                        <% if (helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class) != MessageSendMethod.NONE) { %>
+                        <% if ((boolean)JspUtility.getPwmRequest(pageContext).getAttribute(PwmRequest.Attribute.HelpdeskVerificationEnabled) == true) { %>
                         <button id="sendTokenButton" class="helpdesk-detail-btn btn">
                         <button id="sendTokenButton" class="helpdesk-detail-btn btn">
                             <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-mobile-phone"></span></pwm:if>
                             <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-mobile-phone"></span></pwm:if>
-                            Send Verification
+                            <pwm:display key="Button_Verify"/>
                         </button>
                         </button>
                         <% } %>
                         <% } %>
                         <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_DELETE_USER_BUTTON)) { %>
                         <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_DELETE_USER_BUTTON)) { %>

+ 7 - 1
src/main/webapp/WEB-INF/jsp/helpdesk.jsp

@@ -1,3 +1,4 @@
+<%@ page import="password.pwm.http.JspUtility" %>
 <%--
 <%--
   ~ Password Management Servlets (PWM)
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
   ~ http://www.pwm-project.org
@@ -38,7 +39,7 @@
                         <span class="pwm-icon pwm-icon-search"></span>
                         <span class="pwm-icon pwm-icon-search"></span>
                     </td>
                     </td>
                     <td style="width:400px">
                     <td style="width:400px">
-                        <input type="search" id="username" name="username" class="helpdesk-input-username" style="width: 400px" <pwm:autofocus/> autocomplete="off"/>
+                        <input placeholder="<pwm:display key="Placeholder_Search"/>" type="search" id="username" name="username" class="helpdesk-input-username" style="width: 400px" <pwm:autofocus/> autocomplete="off"/>
                     </td>
                     </td>
                     <td style="width:20px">
                     <td style="width:20px">
                         <div id="searchIndicator" style="display:none">
                         <div id="searchIndicator" style="display:none">
@@ -48,6 +49,11 @@
                             <span style="color: #ffcd59;" class="pwm-icon pwm-icon-lg pwm-icon-exclamation-circle"></span>
                             <span style="color: #ffcd59;" class="pwm-icon pwm-icon-lg pwm-icon-exclamation-circle"></span>
                         </div>
                         </div>
                     </td>
                     </td>
+                    <% if ((boolean)JspUtility.getPwmRequest(pageContext).getAttribute(PwmRequest.Attribute.HelpdeskVerificationEnabled) == true) { %>
+                    <td style="width: 45px">
+                        <button class="btn" id="button-show-current-verifications">Verifications</button>
+                    </td>
+                    <% } %>
                 </tr>
                 </tr>
             </table>
             </table>
             <noscript>
             <noscript>

+ 1 - 1
src/main/webapp/WEB-INF/jsp/peoplesearch.jsp

@@ -34,7 +34,7 @@
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
 
 
         <div id="panel-searchbar">
         <div id="panel-searchbar">
-	        <input id="username" name="username" placeholder="People Search" class="peoplesearch-input-username" <pwm:autofocus/> autocomplete="off" />
+	        <input id="username" name="username" placeholder="<pwm:display key="Placeholder_Search"/>" class="peoplesearch-input-username" <pwm:autofocus/> autocomplete="off" />
             <div style="width:20px; max-width: 20px; display: inline-block;">
             <div style="width:20px; max-width: 20px; display: inline-block;">
                 <div id="searchIndicator" style="display: none">
                 <div id="searchIndicator" style="display: none">
                     <span style="" class="pwm-icon pwm-icon-lg pwm-icon-spin pwm-icon-spinner"></span>
                     <span style="" class="pwm-icon pwm-icon-lg pwm-icon-spin pwm-icon-spinner"></span>

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

@@ -65,7 +65,7 @@ PWM_ADMIN.initAdminNavMenu = function() {
                     label: '<span class="pwm-icon pwm-icon-external-link"></span> Application Reference',
                     label: '<span class="pwm-icon pwm-icon-external-link"></span> Application Reference',
                     id: 'applictionReference_dropitem',
                     id: 'applictionReference_dropitem',
                     onClick: function() {
                     onClick: function() {
-                        PWM_MAIN.newWindowOpen(PWM_GLOBAL['url-context'] + '/public/reference','referencedoc');
+                        PWM_MAIN.newWindowOpen(PWM_GLOBAL['url-context'] + '/public/reference/','referencedoc');
                     }
                     }
                 }));
                 }));
                 if (PWM_GLOBAL['setting-displayEula'] == true) {
                 if (PWM_GLOBAL['setting-displayEula'] == true) {

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

@@ -325,7 +325,7 @@ PWM_CFGEDIT.initConfigEditor = function(nextFunction) {
     PWM_MAIN.addEventHandler('saveButton_icon','click',function(){PWM_CFGEDIT.saveConfiguration()});
     PWM_MAIN.addEventHandler('saveButton_icon','click',function(){PWM_CFGEDIT.saveConfiguration()});
     PWM_MAIN.addEventHandler('setPassword_icon','click',function(){PWM_CFGEDIT.setConfigurationPassword()});
     PWM_MAIN.addEventHandler('setPassword_icon','click',function(){PWM_CFGEDIT.setConfigurationPassword()});
     PWM_MAIN.addEventHandler('referenceDoc_icon','click',function(){
     PWM_MAIN.addEventHandler('referenceDoc_icon','click',function(){
-        PWM_MAIN.newWindowOpen(PWM_GLOBAL['url-context'] + '/public/reference','referencedoc');
+        PWM_MAIN.newWindowOpen(PWM_GLOBAL['url-context'] + '/public/reference/','referencedoc');
     });
     });
     PWM_MAIN.addEventHandler('macroDoc_icon','click',function(){ PWM_CFGEDIT.showMacroHelp(); });
     PWM_MAIN.addEventHandler('macroDoc_icon','click',function(){ PWM_CFGEDIT.showMacroHelp(); });
     PWM_MAIN.addEventHandler('settingFilter_icon','click',function(){ PWM_CFGEDIT.showSettingFilter(); });
     PWM_MAIN.addEventHandler('settingFilter_icon','click',function(){ PWM_CFGEDIT.showSettingFilter(); });

+ 131 - 47
src/main/webapp/public/resources/js/helpdesk.js

@@ -36,7 +36,8 @@ PWM_HELPDESK.executeAction = function(actionName) {
             var inputValues = {};
             var inputValues = {};
             inputValues['userKey'] = PWM_VAR['helpdesk_obfuscatedDN'];
             inputValues['userKey'] = PWM_VAR['helpdesk_obfuscatedDN'];
             PWM_MAIN.showWaitDialog({loadFunction:function() {
             PWM_MAIN.showWaitDialog({loadFunction:function() {
-                var url = "Helpdesk?processAction=executeAction&name=" + actionName;
+                var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", "executeAction");
+                url = PWM_MAIN.addParamToUrl(url, "name", actionName);
                 var loadFunction = function(data) {
                 var loadFunction = function(data) {
                     PWM_MAIN.closeWaitDialog();
                     PWM_MAIN.closeWaitDialog();
                     if (data['error'] == true) {
                     if (data['error'] == true) {
@@ -245,24 +246,85 @@ PWM_HELPDESK.setRandomPasswordPopup = function() {
 };
 };
 
 
 PWM_HELPDESK.loadSearchDetails = function(userKey) {
 PWM_HELPDESK.loadSearchDetails = function(userKey) {
-
-    var gotoDetailFunction = function() {
+    var gotoDetailFunction = function(userKey) {
         PWM_MAIN.showWaitDialog({loadFunction:function() {
         PWM_MAIN.showWaitDialog({loadFunction:function() {
-            PWM_MAIN.submitPostAction('helpdesk','detail',{userKey:userKey});
+            var contents = {};
+            contents['userKey'] = userKey;
+            if (PWM_MAIN.Preferences.readSessionStorage(PREF_KEY_VERIFICATION_STATE)) {
+                contents[PARAM_VERIFICATION_STATE] = PWM_MAIN.Preferences.readSessionStorage(PREF_KEY_VERIFICATION_STATE);
+            }
+            PWM_MAIN.submitPostAction('helpdesk','detail',contents);
         }});
         }});
     };
     };
 
 
-//    PWM_HELPDESK.validateOtpCode(userKey,gotoDetailFunction);
+    var handleVerificationResult = function(data) {
+        if (data['error']) {
+            PWM_MAIN.showErrorDialog(data);
+        } else {
+            if (data['data']['passed']) {
+                gotoDetailFunction(userKey);
+            } else {
+                var verificationMethods = PWM_VAR['verificationMethods']['required'];
+                PWM_HELPDESK.sendVerificationToken(userKey,verificationMethods);
+            }
+        }
+    };
+
+    var checkVerificationFunction = function() {
+        var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", "checkVerification");
+        var content = {};
+        content['userKey'] = userKey;
+        content[PARAM_VERIFICATION_STATE] = PWM_MAIN.Preferences.readSessionStorage(PREF_KEY_VERIFICATION_STATE);
+        PWM_MAIN.ajaxRequest(url, handleVerificationResult, {content:content});
+    };
+
+
+    PWM_MAIN.showWaitDialog({loadFunction:checkVerificationFunction});
+};
+
+PWM_HELPDESK.showRecentVerifications = function() {
+    var handleVerificationResult = function(data) {
+        if (data['error']) {
+            PWM_MAIN.showErrorDialog(data);
+            return;
+        } else {
+            var records = data['data']['records'];
+            var html = '';
+            if (PWM_MAIN.JSLibrary.isEmpty(records)) {
+                html += PWM_MAIN.showString('Display_SearchResultsNone');
+            } else {
+                html += '<table>';
+                html += '<tr><td class="title">Profile</td><td class="title">Username</td><td class="title">Time</td><td class="title">Method</td>';
+                for (var i in records) {
+                    var record = records[i];
+                    html += '<tr>';
+                    html += '<td>' + record['profile'] + '</td>';
+                    html += '<td>' + record['username'] + '</td>';
+                    html += '<td class="timestamp">' + record['timestamp'] + '</td>';
+                    html += '<td>' + record['method'] + '</td>';
+                    html += '</tr>';
+                }
+            }
+
+            html += '</table>';
+            PWM_MAIN.showDialog({'title':'Recent Verifications','text':html,loadFunction:function(){PWM_MAIN.TimestampHandler.initAllElements()}});
+        }
+    };
+
+    var loadVerificationsFunction = function() {
+        var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", "showVerifications");
+        var content = {};
+        content[PARAM_VERIFICATION_STATE] = PWM_MAIN.Preferences.readSessionStorage(PREF_KEY_VERIFICATION_STATE);
+        PWM_MAIN.ajaxRequest(url, handleVerificationResult, {content:content});
+    };
 
 
-    /*
-     */
 
 
-    gotoDetailFunction();
+    PWM_MAIN.showWaitDialog({loadFunction:loadVerificationsFunction});
 };
 };
 
 
 PWM_HELPDESK.processHelpdeskSearch = function() {
 PWM_HELPDESK.processHelpdeskSearch = function() {
     var validationProps = {};
     var validationProps = {};
-    validationProps['serviceURL'] = "Helpdesk?processAction=search";
+    validationProps['serviceURL'] = PWM_MAIN.addParamToUrl(window.location.href,"processAction", "search");
     validationProps['showMessage'] = false;
     validationProps['showMessage'] = false;
     validationProps['ajaxTimeout'] = 120 * 1000;
     validationProps['ajaxTimeout'] = 120 * 1000;
     validationProps['usernameField'] = PWM_MAIN.getObject('username').value;
     validationProps['usernameField'] = PWM_MAIN.getObject('username').value;
@@ -350,13 +412,16 @@ PWM_HELPDESK.deleteUser = function() {
     })
     })
 };
 };
 
 
-PWM_HELPDESK.validateOtpCode = function(userKey, successFunction) {
+PWM_HELPDESK.validateOtpCode = function(userKey) {
     var dialogText = 'Instruct the user to load their mobile authentication app and share the current pass code.';
     var dialogText = 'Instruct the user to load their mobile authentication app and share the current pass code.';
 
 
-    PWM_HELPDESK.validateCode(userKey, successFunction, 'validateOtpCode', dialogText, {})
+    PWM_HELPDESK.validateCode(userKey, 'validateOtpCode', dialogText, {})
 };
 };
 
 
-PWM_HELPDESK.validateCode = function(userKey, successFunction, processAction, dialogText, extraPayload) {
+var PARAM_VERIFICATION_STATE = 'verificationState';
+var PREF_KEY_VERIFICATION_STATE = 'verificiationState';
+
+PWM_HELPDESK.validateCode = function(userKey, processAction, dialogText, extraPayload) {
     var validateOtpCodeFunction = function(){
     var validateOtpCodeFunction = function(){
         PWM_MAIN.getObject('icon-working').style.display = 'inherit';
         PWM_MAIN.getObject('icon-working').style.display = 'inherit';
         PWM_MAIN.getObject('icon-cross').style.display = 'none';
         PWM_MAIN.getObject('icon-cross').style.display = 'none';
@@ -365,6 +430,7 @@ PWM_HELPDESK.validateCode = function(userKey, successFunction, processAction, di
 
 
         content['userKey'] = userKey;
         content['userKey'] = userKey;
         content['code'] = PWM_MAIN.getObject('code').value;
         content['code'] = PWM_MAIN.getObject('code').value;
+        content[PARAM_VERIFICATION_STATE] = PWM_MAIN.Preferences.readSessionStorage(PREF_KEY_VERIFICATION_STATE);
         var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", processAction);
         var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", processAction);
         var loadFunction = function(data) {
         var loadFunction = function(data) {
             PWM_MAIN.getObject('icon-working').style.display = 'none';
             PWM_MAIN.getObject('icon-working').style.display = 'none';
@@ -374,7 +440,11 @@ PWM_HELPDESK.validateCode = function(userKey, successFunction, processAction, di
                 return;
                 return;
             }
             }
 
 
-            var passed =  data['data'];
+            var verificationState = data['data'][PARAM_VERIFICATION_STATE];
+            PWM_MAIN.Preferences.writeSessionStorage(PREF_KEY_VERIFICATION_STATE,verificationState);
+            console.log(verificationState);
+
+            var passed =  data['data']['passed'];
             if (passed) {
             if (passed) {
                 PWM_MAIN.getObject('icon-check').style.display = 'inherit';
                 PWM_MAIN.getObject('icon-check').style.display = 'inherit';
                 PWM_MAIN.getObject('dialog_ok_button').disabled = false;
                 PWM_MAIN.getObject('dialog_ok_button').disabled = false;
@@ -394,7 +464,11 @@ PWM_HELPDESK.validateCode = function(userKey, successFunction, processAction, di
         + '<button type="button" class="btn" id="button-checkCode"><span class="btn-icon pwm-icon pwm-icon-check"></span>' + PWM_MAIN.showString('Button_CheckCode') + '</button>'
         + '<button type="button" class="btn" id="button-checkCode"><span class="btn-icon pwm-icon pwm-icon-check"></span>' + PWM_MAIN.showString('Button_CheckCode') + '</button>'
         + '</td></table></div>';
         + '</td></table></div>';
 
 
-    var successFunction = successFunction === undefined ? function(){} : successFunction;
+    var successFunction = function() {
+        if (PWM_MAIN.getObject('application-info').getAttribute('data-jsp-name') == 'helpdesk.jsp') {
+            PWM_HELPDESK.loadSearchDetails(userKey);
+        }
+    };
     PWM_MAIN.showDialog({
     PWM_MAIN.showDialog({
         showClose:true,
         showClose:true,
         allowMove:true,
         allowMove:true,
@@ -410,22 +484,22 @@ PWM_HELPDESK.validateCode = function(userKey, successFunction, processAction, di
     });
     });
 };
 };
 
 
-PWM_HELPDESK.sendVerificationToken = function(userKey) {
+PWM_HELPDESK.sendVerificationToken = function(userKey, methods) {
     var sendMethodSetting = PWM_VAR["helpdesk_setting_tokenSendMethod"];
     var sendMethodSetting = PWM_VAR["helpdesk_setting_tokenSendMethod"];
-    var choiceFlag = sendMethodSetting == 'CHOICE_SMS_EMAIL';
+    var tokenChoiceFlag = sendMethodSetting == 'CHOICE_SMS_EMAIL';
 
 
     var sendTokenAction = function(choice) {
     var sendTokenAction = function(choice) {
         var sendContent = {};
         var sendContent = {};
         sendContent['userKey'] = userKey;
         sendContent['userKey'] = userKey;
-        if (choiceFlag && choice) {
+        if (tokenChoiceFlag && choice) {
             sendContent['method'] = choice;
             sendContent['method'] = choice;
         }
         }
         PWM_MAIN.showWaitDialog({loadFunction:function(){
         PWM_MAIN.showWaitDialog({loadFunction:function(){
-            var url = 'helpdesk?processAction=sendVerificationToken';
+            var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", "sendVerificationToken");
             var loadFunction = function(data) {
             var loadFunction = function(data) {
                 if (!data['error']) {
                 if (!data['error']) {
                     var text = '<table><tr><td>Token Destination</td><td>' + data['data']['destination'] + '</td></tr></table>';
                     var text = '<table><tr><td>Token Destination</td><td>' + data['data']['destination'] + '</td></tr></table>';
-                    PWM_HELPDESK.validateCode(userKey, function(){}, 'verifyVerificationToken',text,data['data']);
+                    PWM_HELPDESK.validateCode(userKey, 'verifyVerificationToken',text,data['data']);
                 } else {
                 } else {
                     PWM_MAIN.showErrorDialog(data);
                     PWM_MAIN.showErrorDialog(data);
                 }
                 }
@@ -434,34 +508,41 @@ PWM_HELPDESK.sendVerificationToken = function(userKey) {
         }});
         }});
     };
     };
 
 
-    if (choiceFlag) {
-        var confirmText = '<div style="text-align:center"><br/><br/><button class="btn" type="button" name="emailChoiceButton" id="emailChoiceButton">'
-            + '<span class="btn-icon pwm-icon pwm-icon-file-text"></span>' + PWM_MAIN.showString('Button_Email') + '</button>'
-            + '<br/><br/><button class="btn" type="button" name="smsChoiceButton" id="smsChoiceButton">'
-            + '<span class="btn-icon pwm-icon pwm-icon-phone"></span>' + PWM_MAIN.showString('Button_SMS') + '</button></div>';
-        var dialoagLoadFunction = function() {
-            PWM_MAIN.addEventHandler('emailChoiceButton','click',function(){sendTokenAction('email')});
-            PWM_MAIN.addEventHandler('smsChoiceButton','click',function(){sendTokenAction('sms')});
-        };
-        PWM_MAIN.showConfirmDialog({
-            title:'Verification send method',
-            text:confirmText,
-            showOk: !choiceFlag,
-            okAction:function(){
-                sendTokenAction();
-            },
-            loadFunction:dialoagLoadFunction
-        });
-    } else {
-        PWM_MAIN.showConfirmDialog({
-            okAction:function(){
-                sendTokenAction();
-            }
-        });
+    var dialogText = '<div style="text-align:center"><br/>';
+    if (PWM_MAIN.JSLibrary.arrayContains(methods,'TOKEN')) {
+        if (tokenChoiceFlag || sendMethodSetting == 'EMAILONLY') {
+            dialogText += '<br/><button class="btn" type="button" name="emailChoiceButton" id="emailChoiceButton">'
+                + '<span class="btn-icon pwm-icon pwm-icon-envelope-o"></span>' + PWM_MAIN.showString('Button_Email') + '</button>';
+        }
+        if (tokenChoiceFlag || sendMethodSetting == 'EMAILONLY') {
+            dialogText += '<br/><br/><button class="btn" type="button" name="smsChoiceButton" id="smsChoiceButton">'
+                + '<span class="btn-icon pwm-icon pwm-icon-phone"></span>' + PWM_MAIN.showString('Button_SMS') + '</button>';
+        }
     }
     }
+    if (PWM_MAIN.JSLibrary.arrayContains(methods,'OTP')) {
+        dialogText += '<br/><br/><button class="btn" type="button" name="otpChoiceButton" id="otpChoiceButton">'
+        + '<span class="btn-icon pwm-icon pwm-icon-qrcode"></span>' + PWM_MAIN.showString('Button_OTP') + '</button>';
+    }
+    dialogText += '</div>';
+
+    var dialoagLoadFunction = function() {
+        PWM_MAIN.addEventHandler('emailChoiceButton','click',function(){sendTokenAction('email')});
+        PWM_MAIN.addEventHandler('smsChoiceButton','click',function(){sendTokenAction('sms')});
+        PWM_MAIN.addEventHandler('otpChoiceButton','click',function(){PWM_HELPDESK.validateOtpCode(userKey)});
+    };
+    PWM_MAIN.showConfirmDialog({
+        title:'Verification send method',
+        text:dialogText,
+        showOk: !tokenChoiceFlag,
+        okAction:function(){
+            sendTokenAction();
+        },
+        loadFunction:dialoagLoadFunction
+    });
 };
 };
 
 
 PWM_HELPDESK.initHelpdeskSearchPage = function() {
 PWM_HELPDESK.initHelpdeskSearchPage = function() {
+    PWM_MAIN.addEventHandler('button-show-current-verifications','click',PWM_HELPDESK.showRecentVerifications);
     PWM_HELPDESK.makeSearchGrid(function(){
     PWM_HELPDESK.makeSearchGrid(function(){
         PWM_MAIN.addEventHandler('username', "keyup, input", function(){
         PWM_MAIN.addEventHandler('username', "keyup, input", function(){
             PWM_HELPDESK.processHelpdeskSearch();
             PWM_HELPDESK.processHelpdeskSearch();
@@ -516,7 +597,9 @@ PWM_HELPDESK.initHelpdeskDetailPage = function() {
         PWM_HELPDESK.validateOtpCode(PWM_VAR['helpdesk_obfuscatedDN']);
         PWM_HELPDESK.validateOtpCode(PWM_VAR['helpdesk_obfuscatedDN']);
     });
     });
     PWM_MAIN.addEventHandler('sendTokenButton','click',function(){
     PWM_MAIN.addEventHandler('sendTokenButton','click',function(){
-        PWM_HELPDESK.sendVerificationToken(PWM_VAR['helpdesk_obfuscatedDN']);
+
+        var verificationMethods = PWM_VAR['verificationMethods']['optional'];
+        PWM_HELPDESK.sendVerificationToken(PWM_VAR['helpdesk_obfuscatedDN'],verificationMethods);
     });
     });
     PWM_MAIN.addEventHandler('helpdesk_deleteUserButton','click',function(){
     PWM_MAIN.addEventHandler('helpdesk_deleteUserButton','click',function(){
         PWM_HELPDESK.deleteUser();
         PWM_HELPDESK.deleteUser();
@@ -561,7 +644,8 @@ PWM_HELPDESK.unlockIntruder = function() {
         okAction:function() {
         okAction:function() {
             PWM_MAIN.showWaitDialog({
             PWM_MAIN.showWaitDialog({
                 loadFunction:function(){
                 loadFunction:function(){
-                    var ajaxUrl = "helpdesk?processAction=unlockIntruder&userKey=" + PWM_VAR['helpdesk_obfuscatedDN'];
+                    var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", "unlockIntruder");
+                    url = PWM_MAIN.addParamToUrl(url, "userKey", PWM_VAR['helpdesk_obfuscatedDN']);
                     var load = function(data) {
                     var load = function(data) {
                         if (data['error'] == true) {
                         if (data['error'] == true) {
                             PWM_MAIN.showErrorDialog(error);
                             PWM_MAIN.showErrorDialog(error);
@@ -575,7 +659,7 @@ PWM_HELPDESK.unlockIntruder = function() {
                             });
                             });
                         }
                         }
                     };
                     };
-                    PWM_MAIN.ajaxRequest(ajaxUrl, load);
+                    PWM_MAIN.ajaxRequest(url, load);
                 }
                 }
             });
             });
         }
         }
@@ -586,7 +670,7 @@ PWM_HELPDESK.doOtpClear = function() {
     var inputValues = {};
     var inputValues = {};
     inputValues['userKey'] = PWM_VAR['helpdesk_obfuscatedDN'];
     inputValues['userKey'] = PWM_VAR['helpdesk_obfuscatedDN'];
     PWM_MAIN.showWaitDialog({loadFunction:function() {
     PWM_MAIN.showWaitDialog({loadFunction:function() {
-        var url = "helpdesk?processAction=clearOtpSecret";
+        var url = PWM_MAIN.addParamToUrl(window.location.href,"processAction", "clearOtpSecret");
         var loadFunction = function(results) {
         var loadFunction = function(results) {
             if (results['error'] != true) {
             if (results['error'] != true) {
                 PWM_MAIN.showDialog({
                 PWM_MAIN.showDialog({