浏览代码

changepassword, helpdesk UI web service refactoring.

Jason Rivard 7 年之前
父节点
当前提交
03497c28b9

+ 18 - 187
server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -36,6 +36,7 @@ import password.pwm.bean.PasswordStatus;
 import password.pwm.ldap.UserInfo;
 import password.pwm.config.Configuration;
 import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.form.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.RequireCurrentPasswordMode;
@@ -71,6 +72,7 @@ import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestCheckPasswordServer;
+import password.pwm.ws.server.rest.RestRandomPasswordServer;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
@@ -100,6 +102,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         warnResponse(HttpMethod.POST),
         reset(HttpMethod.POST),
         checkPassword(HttpMethod.POST),
+        randomPassword(HttpMethod.POST),
 
         ;
 
@@ -205,7 +208,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         }
 
         try {
-            executeChangePassword(pwmRequest, password1);
+            ChangePasswordServletUtil.executeChangePassword(pwmRequest, password1);
         } catch (PwmOperationalException e) {
             LOGGER.debug(e.getErrorInformation().toDebugStr());
             setLastError(pwmRequest, e.getErrorInformation());
@@ -277,7 +280,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
             final Map<FormConfiguration,String> formValues = FormUtility.readFormValuesFromRequest(
                     pwmRequest, formItem, ssBean.getLocale());
 
-            validateParamsAgainstLDAP(formValues, pwmRequest.getPwmSession(),
+            ChangePasswordServletUtil.validateParamsAgainstLDAP(formValues, pwmRequest.getPwmSession(),
                     pwmRequest.getPwmSession().getSessionManager().getActor(pwmRequest.getPwmApplication()));
 
             cpb.setFormPassed(true);
@@ -378,7 +381,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
                 userInfo
         );
 
-        final RestCheckPasswordServer.JsonData checkResult = RestCheckPasswordServer.doPasswordRuleCheck(
+        final RestCheckPasswordServer.JsonOutput checkResult = RestCheckPasswordServer.doPasswordRuleCheck(
                 pwmRequest.getPwmApplication(),
                 pwmRequest.getPwmSession(),
                 passwordCheckRequest);
@@ -389,38 +392,15 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         return ProcessStatus.Halt;
     }
 
-    private void executeChangePassword(
-            final PwmRequest pwmRequest,
-            final PasswordData newPassword
-    )
-            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
+    @ActionHandler(action = "randomPassword")
+    private ProcessStatus processRandomPasswordAction(final PwmRequest pwmRequest) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        // password accepted, setup change password
-        final ChangePasswordBean cpb = pwmApplication.getSessionStateService().getBean(pwmRequest, ChangePasswordBean.class);
-
-        // change password
-        PasswordUtility.setActorPassword(pwmSession, pwmApplication, newPassword);
-
-        //init values for progress screen
-        {
-            final PasswordChangeProgressChecker.ProgressTracker tracker = new PasswordChangeProgressChecker.ProgressTracker();
-            final PasswordChangeProgressChecker checker = new PasswordChangeProgressChecker(
-                    pwmApplication,
-                    pwmSession.getUserInfo().getUserIdentity(),
-                    pwmSession.getLabel(),
-                    pwmSession.getSessionStateBean().getLocale()
-            );
-            cpb.setChangeProgressTracker(tracker);
-            cpb.setChangePasswordMaxCompletion(checker.maxCompletionTime(tracker));
-        }
-
-        // send user an email confirmation
-        sendChangePasswordEmailNotice(pwmSession, pwmApplication);
-
-        // send audit event
-        pwmApplication.getAuditManager().submit(AuditEvent.CHANGE_PASSWORD, pwmSession.getUserInfo(), pwmSession);
+        final PasswordData passwordData = RandomPasswordGenerator.createRandomPassword(pwmRequest.getPwmSession(), pwmRequest.getPwmApplication());
+        final RestRandomPasswordServer.JsonOutput jsonOutput = new RestRandomPasswordServer.JsonOutput();
+        jsonOutput.setPassword(passwordData.getStringValue());
+        final RestResultBean restResultBean = new RestResultBean(jsonOutput);
+        pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
     public void nextStep(
@@ -439,7 +419,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
             return;
         }
 
-        if (warnPageShouldBeShown(pwmRequest, changePasswordBean)) {
+        if (ChangePasswordServletUtil.warnPageShouldBeShown(pwmRequest, changePasswordBean)) {
             LOGGER.trace(pwmRequest, "password expiration is within password warn period, forwarding user to warning page");
             pwmRequest.forwardToJsp(JspUrl.PASSWORD_WARN);
             return;
@@ -454,7 +434,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
             return;
         }
 
-        if (determineIfCurrentPasswordRequired(pwmApplication, pwmSession) && !changePasswordBean.isCurrentPasswordPassed()) {
+        if (ChangePasswordServletUtil.determineIfCurrentPasswordRequired(pwmApplication, pwmSession) && !changePasswordBean.isCurrentPasswordPassed()) {
             forwardToFormPage(pwmRequest);
             return;
         }
@@ -468,156 +448,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         forwardToChangePage(pwmRequest);
     }
 
-    private static boolean determineIfCurrentPasswordRequired(
-            final PwmApplication pwmApplication,
-            final PwmSession pwmSession
-    )
-            throws PwmUnrecoverableException
-    {
-        final RequireCurrentPasswordMode currentSetting = pwmApplication.getConfig().readSettingAsEnum(PwmSetting.PASSWORD_REQUIRE_CURRENT, RequireCurrentPasswordMode.class);
-
-        if (currentSetting == RequireCurrentPasswordMode.FALSE) {
-            return false;
-        }
 
-        if (pwmSession.getLoginInfoBean().getType() == AuthenticationType.AUTH_FROM_PUBLIC_MODULE) {
-            LOGGER.debug(pwmSession, "skipping user current password requirement, authentication type is " + AuthenticationType.AUTH_FROM_PUBLIC_MODULE);
-            return false;
-        }
-
-        {
-            final PasswordData currentPassword = pwmSession.getLoginInfoBean().getUserCurrentPassword();
-            if (currentPassword == null) {
-                LOGGER.debug(pwmSession, "skipping user current password requirement, current password is not known to application");
-                return false;
-            }
-        }
-
-        if (currentSetting == RequireCurrentPasswordMode.TRUE) {
-            return true;
-        }
-
-        final PasswordStatus passwordStatus = pwmSession.getUserInfo().getPasswordStatus();
-        return currentSetting == RequireCurrentPasswordMode.NOTEXPIRED
-                && !passwordStatus.isExpired()
-                && !passwordStatus.isPreExpired()
-                && !passwordStatus.isViolatesPolicy()
-                && !pwmSession.getUserInfo().isRequiresNewPassword();
-
-    }
-
-    private static void validateParamsAgainstLDAP(
-            final Map<FormConfiguration, String> formValues,
-            final PwmSession pwmSession,
-            final ChaiUser theUser
-    )
-            throws ChaiUnavailableException, PwmDataValidationException
-    {
-        for (final FormConfiguration formItem : formValues.keySet()) {
-            final String attrName = formItem.getName();
-            final String value = formValues.get(formItem);
-            try {
-                if (!theUser.compareStringAttribute(attrName, value)) {
-                    final String errorMsg = "incorrect value for '" + attrName + "'";
-                    final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE, errorMsg, new String[]{attrName});
-                    LOGGER.debug(pwmSession, errorInfo.toDebugStr());
-                    throw new PwmDataValidationException(errorInfo);
-                }
-                LOGGER.trace(pwmSession, "successful validation of ldap value for '" + attrName + "'");
-            } catch (ChaiOperationException e) {
-                LOGGER.error(pwmSession, "error during param validation of '" + attrName + "', error: " + e.getMessage());
-                throw new PwmDataValidationException(new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE, "ldap error testing value for '" + attrName + "'", new String[]{attrName}));
-            }
-        }
-    }
-
-    private static void sendChangePasswordEmailNotice(
-            final PwmSession pwmSession,
-            final PwmApplication pwmApplication
-    )
-            throws PwmUnrecoverableException
-    {
-        final Configuration config = pwmApplication.getConfig();
-        final Locale locale = pwmSession.getSessionStateBean().getLocale();
-        final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_CHANGEPASSWORD, locale);
-
-        if (configuredEmailSetting == null) {
-            LOGGER.debug(pwmSession, "skipping change password email for '" + pwmSession.getUserInfo().getUserIdentity() + "' no email configured");
-            return;
-        }
-
-        pwmApplication.getEmailQueue().submitEmail(
-                configuredEmailSetting,
-                pwmSession.getUserInfo(),
-
-                pwmSession.getSessionManager().getMacroMachine(pwmApplication));
-    }
-
-    private static void checkMinimumLifetime(
-            final PwmApplication pwmApplication,
-            final PwmSession pwmSession,
-            final ChangePasswordBean changePasswordBean,
-            final UserInfo userInfo
-    )
-            throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException
-    {
-        if (changePasswordBean.isNextAllowedTimePassed()) {
-            return;
-        }
-
-        try {
-            PasswordUtility.checkIfPasswordWithinMinimumLifetime(
-                    pwmSession.getSessionManager().getActor(pwmApplication),
-                    pwmSession.getLabel(),
-                    userInfo.getPasswordPolicy(),
-                    userInfo.getPasswordLastModifiedTime(),
-                    userInfo.getPasswordStatus()
-            );
-        } catch (PwmException e) {
-            final boolean enforceFromForgotten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME);
-            if (!enforceFromForgotten && userInfo.isRequiresNewPassword()) {
-                LOGGER.debug(pwmSession, "current password is too young, but skipping enforcement of minimum lifetime check due to setting "
-                        + PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug(null, pwmSession.getSessionStateBean().getLocale()));
-            } else {
-                throw new PwmUnrecoverableException(e.getErrorInformation());
-            }
-        }
-
-        changePasswordBean.setNextAllowedTimePassed(true);
-    }
-
-
-
-    private boolean warnPageShouldBeShown(
-            final PwmRequest pwmRequest,
-            final ChangePasswordBean changePasswordBean
-    )
-            throws PwmUnrecoverableException
-    {
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-
-        if (!pwmSession.getUserInfo().getPasswordStatus().isWarnPeriod()) {
-            return false;
-        }
-
-        if (pwmRequest.getPwmSession().getLoginInfoBean().isLoginFlag(LoginInfoBean.LoginFlag.skipNewPw)) {
-            return false;
-        }
-
-        if (changePasswordBean.isWarnPassed()) {
-            return false;
-        }
-
-        if (pwmRequest.getPwmSession().getLoginInfoBean().getAuthFlags().contains(AuthenticationType.AUTH_FROM_PUBLIC_MODULE)) {
-            return false;
-        }
-
-        if (pwmRequest.getPwmSession().getLoginInfoBean().getType() == AuthenticationType.AUTH_FROM_PUBLIC_MODULE) {
-            return false;
-        }
-
-        return true;
-    }
 
     private void forwardToFormPage(final PwmRequest pwmRequest)
             throws ServletException, PwmUnrecoverableException, IOException
@@ -661,7 +492,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
 
         }
 
-        if (determineIfCurrentPasswordRequired(pwmApplication, pwmSession)) {
+        if (ChangePasswordServletUtil.determineIfCurrentPasswordRequired(pwmApplication, pwmSession)) {
             changePasswordBean.setCurrentPasswordRequired(true);
         }
 
@@ -671,7 +502,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         }
 
         try {
-            checkMinimumLifetime(pwmApplication, pwmSession, changePasswordBean, pwmSession.getUserInfo());
+            ChangePasswordServletUtil.checkMinimumLifetime(pwmApplication, pwmSession, changePasswordBean, pwmSession.getUserInfo());
         } catch (PwmOperationalException e) {
             throw new PwmUnrecoverableException(e.getErrorInformation());
         } catch (ChaiException e) {

+ 220 - 0
server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java

@@ -0,0 +1,220 @@
+package password.pwm.http.servlet.changepw;
+
+import com.novell.ldapchai.ChaiUser;
+import com.novell.ldapchai.exception.ChaiOperationException;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import password.pwm.PwmApplication;
+import password.pwm.bean.EmailItemBean;
+import password.pwm.bean.LoginInfoBean;
+import password.pwm.bean.PasswordStatus;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.option.RequireCurrentPasswordMode;
+import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmDataValidationException;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmSession;
+import password.pwm.http.bean.ChangePasswordBean;
+import password.pwm.ldap.PasswordChangeProgressChecker;
+import password.pwm.ldap.UserInfo;
+import password.pwm.ldap.auth.AuthenticationType;
+import password.pwm.svc.event.AuditEvent;
+import password.pwm.util.PasswordData;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.operations.PasswordUtility;
+
+import java.util.Locale;
+import java.util.Map;
+
+public class ChangePasswordServletUtil {
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass(ChangePasswordServletUtil.class);
+
+    static boolean determineIfCurrentPasswordRequired(
+            final PwmApplication pwmApplication,
+            final PwmSession pwmSession
+    )
+            throws PwmUnrecoverableException
+    {
+        final RequireCurrentPasswordMode currentSetting = pwmApplication.getConfig().readSettingAsEnum(PwmSetting.PASSWORD_REQUIRE_CURRENT, RequireCurrentPasswordMode.class);
+
+        if (currentSetting == RequireCurrentPasswordMode.FALSE) {
+            return false;
+        }
+
+        if (pwmSession.getLoginInfoBean().getType() == AuthenticationType.AUTH_FROM_PUBLIC_MODULE) {
+            LOGGER.debug(pwmSession, "skipping user current password requirement, authentication type is " + AuthenticationType.AUTH_FROM_PUBLIC_MODULE);
+            return false;
+        }
+
+        {
+            final PasswordData currentPassword = pwmSession.getLoginInfoBean().getUserCurrentPassword();
+            if (currentPassword == null) {
+                LOGGER.debug(pwmSession, "skipping user current password requirement, current password is not known to application");
+                return false;
+            }
+        }
+
+        if (currentSetting == RequireCurrentPasswordMode.TRUE) {
+            return true;
+        }
+
+        final PasswordStatus passwordStatus = pwmSession.getUserInfo().getPasswordStatus();
+        return currentSetting == RequireCurrentPasswordMode.NOTEXPIRED
+                && !passwordStatus.isExpired()
+                && !passwordStatus.isPreExpired()
+                && !passwordStatus.isViolatesPolicy()
+                && !pwmSession.getUserInfo().isRequiresNewPassword();
+
+    }
+
+    static void validateParamsAgainstLDAP(
+            final Map<FormConfiguration, String> formValues,
+            final PwmSession pwmSession,
+            final ChaiUser theUser
+    )
+            throws ChaiUnavailableException, PwmDataValidationException
+    {
+        for (final FormConfiguration formItem : formValues.keySet()) {
+            final String attrName = formItem.getName();
+            final String value = formValues.get(formItem);
+            try {
+                if (!theUser.compareStringAttribute(attrName, value)) {
+                    final String errorMsg = "incorrect value for '" + attrName + "'";
+                    final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE, errorMsg, new String[]{attrName});
+                    LOGGER.debug(pwmSession, errorInfo.toDebugStr());
+                    throw new PwmDataValidationException(errorInfo);
+                }
+                LOGGER.trace(pwmSession, "successful validation of ldap value for '" + attrName + "'");
+            } catch (ChaiOperationException e) {
+                LOGGER.error(pwmSession, "error during param validation of '" + attrName + "', error: " + e.getMessage());
+                throw new PwmDataValidationException(new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE, "ldap error testing value for '" + attrName + "'", new String[]{attrName}));
+            }
+        }
+    }
+
+    static void sendChangePasswordEmailNotice(
+            final PwmSession pwmSession,
+            final PwmApplication pwmApplication
+    )
+            throws PwmUnrecoverableException
+    {
+        final Configuration config = pwmApplication.getConfig();
+        final Locale locale = pwmSession.getSessionStateBean().getLocale();
+        final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_CHANGEPASSWORD, locale);
+
+        if (configuredEmailSetting == null) {
+            LOGGER.debug(pwmSession, "skipping change password email for '" + pwmSession.getUserInfo().getUserIdentity() + "' no email configured");
+            return;
+        }
+
+        pwmApplication.getEmailQueue().submitEmail(
+                configuredEmailSetting,
+                pwmSession.getUserInfo(),
+
+                pwmSession.getSessionManager().getMacroMachine(pwmApplication));
+    }
+
+    static void checkMinimumLifetime(
+            final PwmApplication pwmApplication,
+            final PwmSession pwmSession,
+            final ChangePasswordBean changePasswordBean,
+            final UserInfo userInfo
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException
+    {
+        if (changePasswordBean.isNextAllowedTimePassed()) {
+            return;
+        }
+
+        try {
+            PasswordUtility.checkIfPasswordWithinMinimumLifetime(
+                    pwmSession.getSessionManager().getActor(pwmApplication),
+                    pwmSession.getLabel(),
+                    userInfo.getPasswordPolicy(),
+                    userInfo.getPasswordLastModifiedTime(),
+                    userInfo.getPasswordStatus()
+            );
+        } catch (PwmException e) {
+            final boolean enforceFromForgotten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME);
+            if (!enforceFromForgotten && userInfo.isRequiresNewPassword()) {
+                LOGGER.debug(pwmSession, "current password is too young, but skipping enforcement of minimum lifetime check due to setting "
+                        + PwmSetting.CHALLENGE_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug(null, pwmSession.getSessionStateBean().getLocale()));
+            } else {
+                throw new PwmUnrecoverableException(e.getErrorInformation());
+            }
+        }
+
+        changePasswordBean.setNextAllowedTimePassed(true);
+    }
+
+    static void executeChangePassword(
+            final PwmRequest pwmRequest,
+            final PasswordData newPassword
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        // password accepted, setup change password
+        final ChangePasswordBean cpb = pwmApplication.getSessionStateService().getBean(pwmRequest, ChangePasswordBean.class);
+
+        // change password
+        PasswordUtility.setActorPassword(pwmSession, pwmApplication, newPassword);
+
+        //init values for progress screen
+        {
+            final PasswordChangeProgressChecker.ProgressTracker tracker = new PasswordChangeProgressChecker.ProgressTracker();
+            final PasswordChangeProgressChecker checker = new PasswordChangeProgressChecker(
+                    pwmApplication,
+                    pwmSession.getUserInfo().getUserIdentity(),
+                    pwmSession.getLabel(),
+                    pwmSession.getSessionStateBean().getLocale()
+            );
+            cpb.setChangeProgressTracker(tracker);
+            cpb.setChangePasswordMaxCompletion(checker.maxCompletionTime(tracker));
+        }
+
+        // send user an email confirmation
+        ChangePasswordServletUtil.sendChangePasswordEmailNotice(pwmSession, pwmApplication);
+
+        // send audit event
+        pwmApplication.getAuditManager().submit(AuditEvent.CHANGE_PASSWORD, pwmSession.getUserInfo(), pwmSession);
+    }
+
+    static boolean warnPageShouldBeShown(
+            final PwmRequest pwmRequest,
+            final ChangePasswordBean changePasswordBean
+    )
+            throws PwmUnrecoverableException
+    {
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+
+        if (!pwmSession.getUserInfo().getPasswordStatus().isWarnPeriod()) {
+            return false;
+        }
+
+        if (pwmRequest.getPwmSession().getLoginInfoBean().isLoginFlag(LoginInfoBean.LoginFlag.skipNewPw)) {
+            return false;
+        }
+
+        if (changePasswordBean.isWarnPassed()) {
+            return false;
+        }
+
+        if (pwmRequest.getPwmSession().getLoginInfoBean().getAuthFlags().contains(AuthenticationType.AUTH_FROM_PUBLIC_MODULE)) {
+            return false;
+        }
+
+        if (pwmRequest.getPwmSession().getLoginInfoBean().getType() == AuthenticationType.AUTH_FROM_PUBLIC_MODULE) {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 282 - 221
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -58,8 +58,9 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.LdapPermissionTester;
+import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.UserInfo;
+import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchResults;
@@ -70,16 +71,23 @@ import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.token.TokenService;
+import password.pwm.util.PasswordData;
+import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.ActionExecutor;
+import password.pwm.util.operations.CrService;
 import password.pwm.util.operations.OtpService;
+import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.otp.OTPUserRecord;
 import password.pwm.util.secure.SecureService;
 import password.pwm.ws.server.RestResultBean;
+import password.pwm.ws.server.rest.RestCheckPasswordServer;
+import password.pwm.ws.server.rest.RestRandomPasswordServer;
+import password.pwm.ws.server.rest.RestSetPasswordServer;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -128,17 +136,20 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         checkVerification(HttpMethod.POST),
         showVerifications(HttpMethod.POST),
         validateAttributes(HttpMethod.POST),
+        clearResponses(HttpMethod.POST),
+        checkPassword(HttpMethod.POST),
+        setPassword(HttpMethod.POST),
+        randomPassword(HttpMethod.POST),
+
         ;
 
         private final HttpMethod method;
 
-        HelpdeskAction(final HttpMethod method)
-        {
+        HelpdeskAction(final HttpMethod method) {
             this.method = method;
         }
 
-        public Collection<HttpMethod> permittedMethods()
-        {
+        public Collection<HttpMethod> permittedMethods() {
             return Collections.singletonList(method);
         }
     }
@@ -148,16 +159,14 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     }
 
 
-    private HelpdeskProfile getHelpdeskRProfile(final PwmRequest pwmRequest) throws PwmUnrecoverableException
-    {
+    private HelpdeskProfile getHelpdeskProfile(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
         return pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmRequest.getPwmApplication());
     }
 
     @Override
     protected void nextStep(final PwmRequest pwmRequest)
-            throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskVerificationEnabled, !helpdeskProfile.readRequiredVerificationMethods().isEmpty());
         pwmRequest.forwardToJsp(JspUrl.HELPDESK_SEARCH);
     }
@@ -172,7 +181,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
 
         if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE)) {
-            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "Setting " + PwmSetting.HELPDESK_ENABLE.toMenuLocationDebug(null,null) + " is not enabled."));
+            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "Setting " + PwmSetting.HELPDESK_ENABLE.toMenuLocationDebug(null, null) + " is not enabled."));
             return ProcessStatus.Halt;
         }
 
@@ -188,7 +197,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     @ActionHandler(action = "clientData")
     private ProcessStatus restClientData(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         final HelpdeskClientDataBean returnValues = new HelpdeskClientDataBean();
         { // search page
             final List<FormConfiguration> searchForm = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
@@ -200,13 +209,13 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
         { /// detail page
             returnValues.setHelpdesk_setting_maskPasswords(helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_PASSWORD_MASKVALUE));
-            returnValues.setHelpdesk_setting_clearResponses(helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_CLEAR_RESPONSES,HelpdeskClearResponseMode.class));
-            returnValues.setHelpdesk_setting_PwUiMode(helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_SET_PASSWORD_MODE,HelpdeskUIMode.class));
+            returnValues.setHelpdesk_setting_clearResponses(helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskClearResponseMode.class));
+            returnValues.setHelpdesk_setting_PwUiMode(helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_SET_PASSWORD_MODE, HelpdeskUIMode.class));
             returnValues.setHelpdesk_setting_tokenSendMethod(helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class));
         }
         { //actions
             final List<ActionConfiguration> actionConfigurations = helpdeskProfile.readSettingAsAction(PwmSetting.HELPDESK_ACTIONS);
-            final Map<String,HelpdeskClientDataBean.ActionInformation> actions = new LinkedHashMap<>();
+            final Map<String, HelpdeskClientDataBean.ActionInformation> actions = new LinkedHashMap<>();
             for (final ActionConfiguration actionConfiguration : actionConfigurations) {
                 final HelpdeskClientDataBean.ActionInformation actionInformation = new HelpdeskClientDataBean.ActionInformation();
                 actionInformation.setName(actionConfiguration.getName());
@@ -217,7 +226,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
             returnValues.setActions(actions);
         }
         {
-            final Map<String,Collection<IdentityVerificationMethod>> verificationMethodsMap = new HashMap<>();
+            final Map<String, Collection<IdentityVerificationMethod>> verificationMethodsMap = new HashMap<>();
             verificationMethodsMap.put("optional", helpdeskProfile.readOptionalVerificationMethods());
             verificationMethodsMap.put("required", helpdeskProfile.readRequiredVerificationMethods());
             returnValues.setVerificationMethods(verificationMethodsMap);
@@ -247,12 +256,11 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus processExecuteActionRequest(
             final PwmRequest pwmRequest
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         final String userKey = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation).get("userKey");
         if (userKey == null || userKey.length() < 1) {
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing");
             setLastError(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
             return ProcessStatus.Halt;
@@ -264,14 +272,14 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         final String requestedName = pwmRequest.readParameterAsString("name");
         ActionConfiguration action = null;
         for (final ActionConfiguration loopAction : actionConfigurations) {
-            if (requestedName !=null && requestedName.equals(loopAction.getName())) {
+            if (requestedName != null && requestedName.equals(loopAction.getName())) {
                 action = loopAction;
                 break;
             }
         }
         if (action == null) {
             final String errorMsg = "request to execute unknown action: " + requestedName;
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMsg);
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
             LOGGER.debug(pwmRequest, errorInformation.toDebugStr());
             final RestResultBean restResultBean = RestResultBean.fromError(errorInformation, pwmRequest);
             pwmRequest.outputJsonResult(restResultBean);
@@ -279,7 +287,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
 
         // check if user should be seen by actor
-        checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
 
         final boolean useProxy = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY);
         try {
@@ -289,12 +297,12 @@ public class HelpdeskServlet extends ControlledPwmServlet {
                     pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity) :
                     pwmRequest.getPwmSession().getSessionManager().getActor(pwmRequest.getPwmApplication(), userIdentity);
             final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
-            final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmRequest.getPwmApplication(),chaiUser)
+            final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmRequest.getPwmApplication(), chaiUser)
                     .setExpandPwmMacros(true)
                     .setMacroMachine(macroMachine)
                     .createActionExecutor();
 
-            actionExecutor.executeAction(action,pwmRequest.getPwmSession());
+            actionExecutor.executeAction(action, pwmRequest.getPwmSession());
 
             // mark the event log
             {
@@ -324,15 +332,14 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus restDeleteUserRequest(
             final PwmRequest pwmRequest
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
 
         final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey.length() < 1) {
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing");
             setLastError(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
             return ProcessStatus.Halt;
@@ -342,7 +349,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         LOGGER.info(pwmSession, "received deleteUser request by " + pwmSession.getUserInfo().getUserIdentity().toString() + " for user " + userIdentity.toString());
 
         // check if user should be seen by actor
-        checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
 
         // read the userID for later logging.
         String userID = null;
@@ -390,7 +397,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
 
         LOGGER.info(pwmSession, "user " + userIdentity + " has been deleted");
         final RestResultBean restResultBean = new RestResultBean();
-        restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmSession.getSessionStateBean().getLocale(),Message.Success_Unknown,pwmApplication.getConfig()));
+        restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig()));
         pwmRequest.outputJsonResult(restResultBean);
         return ProcessStatus.Halt;
     }
@@ -399,9 +406,8 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus processDetailRequest(
             final PwmRequest pwmRequest
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey.length() < 1) {
             pwmRequest.respondWithError(
@@ -410,7 +416,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
 
         final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication()).canonicalized(pwmRequest.getPwmApplication());
-        processDetailRequest(pwmRequest, helpdeskProfile, userIdentity);
+        HelpdeskServletUtil.processDetailRequest(pwmRequest, helpdeskProfile, userIdentity);
         final HelpdeskAuditRecord auditRecord = new AuditRecordFactory(pwmRequest).createHelpdeskAuditRecord(
                 AuditEvent.HELPDESK_VIEW_DETAIL,
                 pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
@@ -423,66 +429,21 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         return ProcessStatus.Halt;
     }
 
-    private void processDetailRequest(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile,
-            final UserIdentity userIdentity
-    )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
-    {
-        final UserIdentity actorUserIdentity = pwmRequest.getUserInfoIfLoggedIn().canonicalized(pwmRequest.getPwmApplication());
-
-        if (actorUserIdentity.canonicalEquals(userIdentity, pwmRequest.getPwmApplication())) {
-            final String errorMsg = "cannot select self";
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,errorMsg);
-            LOGGER.debug(pwmRequest, errorInformation);
-            pwmRequest.respondWithError(errorInformation, false);
-            return;
-        }
-        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 = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
-        pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskDetail, helpdeskDetailInfoBean);
-
-        if (helpdeskDetailInfoBean != null && helpdeskDetailInfoBean.getUserInfo() != null) {
-            final String obfuscatedDN = helpdeskDetailInfoBean.getUserInfo().getUserIdentity().toObfuscatedKey(pwmRequest.getPwmApplication());
-            pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskObfuscatedDN, obfuscatedDN);
-            pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskUsername, helpdeskDetailInfoBean.getUserInfo().getUsername());
-        }
-
-        StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_USER_LOOKUP);
-        pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskVerificationEnabled, !helpdeskProfile.readOptionalVerificationMethods().isEmpty());
-        pwmRequest.forwardToJsp(JspUrl.HELPDESK_DETAIL);
-    }
 
     @ActionHandler(action = "search")
     private ProcessStatus restSearchRequest(
             final PwmRequest pwmRequest
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap();
         final String username = valueMap.get("username");
 
         final boolean useProxy = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY);
         final List<FormConfiguration> searchForm = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
-        final int maxResults = (int)helpdeskProfile.readSettingAsLong(PwmSetting.HELPDESK_RESULT_LIMIT);
+        final int maxResults = (int) helpdeskProfile.readSettingAsLong(PwmSetting.HELPDESK_RESULT_LIMIT);
 
-        if (username == null ||username.isEmpty()) {
+        if (username == null || username.isEmpty()) {
             final HelpdeskSearchResultsBean emptyResults = new HelpdeskSearchResultsBean();
             emptyResults.setSearchResults(new ArrayList<>());
             emptyResults.setSizeExceeded(false);
@@ -502,7 +463,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
             builder.enableContextValidation(false);
             builder.username(username);
             builder.enableValueEscaping(false);
-            builder.filter(getSearchFilter(pwmRequest.getConfig(), helpdeskProfile));
+            builder.filter(HelpdeskServletUtil.getSearchFilter(pwmRequest.getConfig(), helpdeskProfile));
             builder.enableSplitWhitespace(true);
 
             if (!useProxy) {
@@ -515,7 +476,6 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
 
 
-
         final UserSearchResults results;
         final boolean sizeExceeded;
         try {
@@ -533,7 +493,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
 
         final RestResultBean restResultBean = new RestResultBean();
         final HelpdeskSearchResultsBean outputData = new HelpdeskSearchResultsBean();
-        outputData.setSearchResults(results.resultsAsJsonOutput(pwmRequest.getPwmApplication(),pwmRequest.getUserInfoIfLoggedIn()));
+        outputData.setSearchResults(results.resultsAsJsonOutput(pwmRequest.getPwmApplication(), pwmRequest.getUserInfoIfLoggedIn()));
         outputData.setSizeExceeded(sizeExceeded);
         restResultBean.setData(outputData);
         pwmRequest.outputJsonResult(restResultBean);
@@ -544,12 +504,11 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus restUnlockIntruder(
             final PwmRequest pwmRequest
     )
-            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey.length() < 1) {
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing");
             pwmRequest.respondWithError(errorInformation, false);
             return ProcessStatus.Halt;
         }
@@ -569,7 +528,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
 
         // send notice email
-        sendUnlockNoticeEmail(pwmRequest, helpdeskProfile, userIdentity);
+        HelpdeskServletUtil.sendUnlockNoticeEmail(pwmRequest, helpdeskProfile, userIdentity);
 
         final boolean useProxy = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY);
         try {
@@ -604,7 +563,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
 
         final RestResultBean restResultBean = new RestResultBean();
-        restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmRequest.getLocale(),Message.Success_Unknown,pwmRequest.getConfig()));
+        restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmRequest.getLocale(), Message.Success_Unknown, pwmRequest.getConfig()));
         pwmRequest.outputJsonResult(restResultBean);
         return ProcessStatus.Halt;
     }
@@ -613,9 +572,8 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus restValidateOtpCodeRequest(
             final PwmRequest pwmRequest
     )
-            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
 
         final Instant startTime = Instant.now();
 
@@ -625,7 +583,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         );
         final String userKey = helpdeskVerificationRequestBean.getUserKey();
         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);
             return ProcessStatus.Halt;
         }
@@ -697,18 +655,17 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus restSendVerificationTokenRequest(
             final PwmRequest pwmRequest
     )
-            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
 
         final Instant startTime = Instant.now();
         final Configuration config = pwmRequest.getConfig();
-        final Map<String,String> bodyParams = pwmRequest.readBodyAsJsonStringMap();
+        final Map<String, String> bodyParams = pwmRequest.readBodyAsJsonStringMap();
         MessageSendMethod tokenSendMethod = helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class);
         if (tokenSendMethod == MessageSendMethod.CHOICE_SMS_EMAIL) {
             final String METHOD_PARAM_NAME = "method";
             if (bodyParams != null && bodyParams.containsKey(METHOD_PARAM_NAME)) {
-                final String methodParam = bodyParams.getOrDefault(METHOD_PARAM_NAME,"");
+                final String methodParam = bodyParams.getOrDefault(METHOD_PARAM_NAME, "");
                 switch (methodParam) {
                     case "sms":
                         tokenSendMethod = MessageSendMethod.SMSONLY;
@@ -724,8 +681,8 @@ public class HelpdeskServlet extends ControlledPwmServlet {
             }
             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 indication from operator");
-                LOGGER.error(pwmRequest,errorInformation);
-                pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation,pwmRequest));
+                LOGGER.error(pwmRequest, errorInformation);
+                pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
                 return ProcessStatus.Halt;
             }
         }
@@ -735,8 +692,8 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
         if (helpdeskDetailInfoBean == null) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "unable to read helpdesk detail data for specified user");
-            LOGGER.error(pwmRequest,errorInformation);
-            pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation,pwmRequest));
+            LOGGER.error(pwmRequest, errorInformation);
+            pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
             return ProcessStatus.Halt;
         }
         final UserInfo userInfo = helpdeskDetailInfoBean.getUserInfo();
@@ -779,11 +736,11 @@ public class HelpdeskServlet extends ControlledPwmServlet {
             );
         } catch (PwmException e) {
             LOGGER.error(pwmRequest, e.getErrorInformation());
-            pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation(),pwmRequest));
+            pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation(), pwmRequest));
             return ProcessStatus.Halt;
         }
 
-        StatisticsManager.incrementStat(pwmRequest,Statistic.HELPDESK_TOKENS_SENT);
+        StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_TOKENS_SENT);
         final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = new HelpdeskVerificationRequestBean();
         helpdeskVerificationRequestBean.setDestination(destDisplayString.toString());
         helpdeskVerificationRequestBean.setUserKey(bodyParams.get("userKey"));
@@ -811,8 +768,7 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus restVerifyVerificationTokenRequest(
             final PwmRequest pwmRequest
     )
-            throws IOException, PwmUnrecoverableException, ServletException
-    {
+            throws IOException, PwmUnrecoverableException, ServletException {
         final Instant startTime = Instant.now();
         final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonUtil.deserialize(
                 pwmRequest.readRequestBodyAsString(),
@@ -885,12 +841,11 @@ public class HelpdeskServlet extends ControlledPwmServlet {
     private ProcessStatus restClearOtpSecret(
             final PwmRequest pwmRequest
     )
-            throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
 
-        final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
-        final UserIdentity userIdentity = userIdentityFromMap(pwmRequest, bodyMap);
+        final Map<String, String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
+        final UserIdentity userIdentity = HelpdeskServletUtil.userIdentityFromMap(pwmRequest, bodyMap);
 
         if (!helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_CLEAR_OTP_BUTTON)) {
             final String errorMsg = "clear otp request, but helpdesk clear otp button is not enabled";
@@ -928,111 +883,55 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         }
 
         final RestResultBean restResultBean = new RestResultBean();
-        restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmRequest.getLocale(),Message.Success_Unknown,pwmRequest.getConfig()));
+        restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmRequest.getLocale(), Message.Success_Unknown, pwmRequest.getConfig()));
         pwmRequest.outputJsonResult(restResultBean);
         return ProcessStatus.Halt;
     }
 
-    private static String getSearchFilter(final Configuration configuration, final HelpdeskProfile helpdeskProfile) {
-        final String configuredFilter = helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_SEARCH_FILTER);
-        if (configuredFilter != null && !configuredFilter.isEmpty()) {
-            return configuredFilter;
-        }
-
-        final List<String> defaultObjectClasses = configuration.readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES);
-        final List<FormConfiguration> searchAttributes = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
-        final StringBuilder filter = new StringBuilder();
-        filter.append("(&"); //open AND clause for objectclasses and attributes
-        for (final String objectClass : defaultObjectClasses) {
-            filter.append("(objectClass=").append(objectClass).append(")");
-        }
-        filter.append("(|"); // open OR clause for attributes
-        for (final FormConfiguration formConfiguration : searchAttributes) {
-            if (formConfiguration != null && formConfiguration.getName() != null) {
-                final String searchAttribute = formConfiguration.getName();
-                filter.append("(").append(searchAttribute).append("=*").append(PwmConstants.VALUE_REPLACEMENT_USERNAME).append("*)");
-            }
-        }
-        filter.append(")"); // close OR clause
-        filter.append(")"); // close AND clause
-        return filter.toString();
-    }
-
 
     static ChaiUser getChaiUser(
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,
             final UserIdentity userIdentity
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException
-    {
+            throws ChaiUnavailableException, PwmUnrecoverableException {
         final boolean useProxy = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_USE_PROXY);
         return useProxy ?
                 pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity) :
                 pwmRequest.getPwmSession().getSessionManager().getActor(pwmRequest.getPwmApplication(), userIdentity);
     }
 
-    private static void checkIfUserIdentityViewable(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException {
-        final String filterSetting = getSearchFilter(pwmRequest.getConfig(), helpdeskProfile);
-        String filterString = filterSetting.replace(PwmConstants.VALUE_REPLACEMENT_USERNAME, "*");
-        while (filterString.contains("**")) {
-            filterString = filterString.replace("**", "*");
-        }
-
-        final boolean match = LdapPermissionTester.testQueryMatch(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userIdentity, filterString);
-        if (!match) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "requested userDN is not available within configured search filter"));
-        }
-    }
 
     @ActionHandler(action = "checkVerification")
     private ProcessStatus restCheckVerification(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException, ServletException {
 
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
-        final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
+        final Map<String, String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
 
-        final UserIdentity userIdentity = userIdentityFromMap(pwmRequest, bodyMap);
-        checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+        final UserIdentity userIdentity = HelpdeskServletUtil.userIdentityFromMap(pwmRequest, bodyMap);
+        HelpdeskServletUtil.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);
+        final boolean passed = HelpdeskServletUtil.checkIfRequiredVerificationPassed(userIdentity, state, helpdeskProfile);
+        final HashMap<String, Object> results = new HashMap<>();
+        results.put("passed", passed);
         final RestResultBean restResultBean = new RestResultBean(results);
         pwmRequest.outputJsonResult(restResultBean);
         return ProcessStatus.Halt;
     }
 
-    private boolean checkIfRequiredVerificationPassed(final UserIdentity userIdentity, final HelpdeskVerificationStateBean verificationStateBean, final HelpdeskProfile helpdeskProfile) {
-        final Collection<IdentityVerificationMethod> requiredMethods = helpdeskProfile.readRequiredVerificationMethods();
-        if (requiredMethods == null || requiredMethods.isEmpty()) {
-            return true;
-        }
-        for (final IdentityVerificationMethod method : requiredMethods) {
-            if (verificationStateBean.hasRecord(userIdentity, method)) {
-                return true;
-            }
-        }
-        return false;
-    }
 
     @ActionHandler(action = "showVerifications")
     private ProcessStatus restShowVerifications(final PwmRequest pwmRequest)
-            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
-    {
-        final Map<String,String> bodyMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
+            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<>();
+        final HashMap<String, Object> results = new HashMap<>();
         try {
-            results.put("records",state.asViewableValidationRecords(pwmRequest.getPwmApplication(), pwmRequest.getLocale()));
+            results.put("records", state.asViewableValidationRecords(pwmRequest.getPwmApplication(), pwmRequest.getLocale()));
         } catch (ChaiOperationException e) {
             throw PwmUnrecoverableException.fromChaiException(e);
         }
@@ -1041,21 +940,10 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         return ProcessStatus.Halt;
     }
 
-    private UserIdentity userIdentityFromMap(final PwmRequest pwmRequest, final Map<String,String> bodyMap) throws PwmUnrecoverableException {
-        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");
-            throw new PwmUnrecoverableException(errorInformation);
-        }
-
-        return UserIdentity.fromObfuscatedKey(userKey, pwmRequest.getPwmApplication());
-    }
-
     @ActionHandler(action = "validateAttributes")
     private ProcessStatus restValidateAttributes(final PwmRequest pwmRequest)
-            throws IOException, PwmUnrecoverableException, ServletException
-    {
-        final HelpdeskProfile helpdeskProfile = getHelpdeskRProfile(pwmRequest);
+            throws IOException, PwmUnrecoverableException, ServletException {
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
         final Instant startTime = Instant.now();
         final String bodyString = pwmRequest.readRequestBodyAsString();
         final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonUtil.deserialize(
@@ -1069,11 +957,11 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         {
             final List<FormConfiguration> verificationForms = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_VERIFICATION_FORM);
             if (verificationForms == null || verificationForms.isEmpty()) {
-                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,"attempt to verify ldap attributes with no ldap verification attributes configured");
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG, "attempt to verify ldap attributes with no ldap verification attributes configured");
                 throw new PwmUnrecoverableException(errorInformation);
             }
 
-            final Map<String,String> bodyMap = JsonUtil.deserializeStringMap(bodyString);
+            final Map<String, String> bodyMap = JsonUtil.deserializeStringMap(bodyString);
             final ChaiUser chaiUser;
             try {
                 chaiUser = getChaiUser(pwmRequest, helpdeskProfile, userIdentity);
@@ -1137,34 +1025,207 @@ public class HelpdeskServlet extends ControlledPwmServlet {
         return ProcessStatus.Halt;
     }
 
-    private static void sendUnlockNoticeEmail(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final Configuration config = pwmRequest.getConfig();
-        final Locale locale = pwmRequest.getLocale();
-        final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_HELPDESK_UNLOCK, locale);
+    @ActionHandler(action = "clearResponses")
+    private ProcessStatus restClearResponsesHandler(final PwmRequest pwmRequest)
+            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException, PwmOperationalException
+    {
+        final UserIdentity userIdentity;
+        try {
+            userIdentity = readUserKeyRequestParameter(pwmRequest);
+        } catch (PwmUnrecoverableException e) {
+            pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation()));
+            return ProcessStatus.Halt;
+        }
+
+        final HelpdeskProfile helpdeskProfile = pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmRequest.getPwmApplication());
+        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+
+        {
+            final boolean buttonEnabled = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON);
+            final HelpdeskClearResponseMode mode = helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskClearResponseMode.class);
+            if (!buttonEnabled && (mode == HelpdeskClearResponseMode.no)) {
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SECURITY_VIOLATION,"setting "
+                        + PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON.toMenuLocationDebug(helpdeskProfile.getIdentifier(), pwmRequest.getLocale())
+                        + " must be enabled or setting "
+                        + PwmSetting.HELPDESK_CLEAR_RESPONSES.toMenuLocationDebug(helpdeskProfile.getIdentifier(), pwmRequest.getLocale())
+                        + "must be set to yes or ask"));
+            }
+        }
+
+        final ChaiUser chaiUser = getChaiUser(pwmRequest, helpdeskProfile, userIdentity);
+        final String userGUID = LdapOperationsHelper.readLdapGuidValue(
+                pwmRequest.getPwmApplication(),
+                pwmRequest.getPwmSession().getLabel(),
+                userIdentity,
+                true);
 
-        if (configuredEmailSetting == null) {
-            LOGGER.debug(pwmRequest, "skipping send helpdesk unlock notice email for '" + userIdentity + "' no email configured");
-            return;
+        final CrService crService = pwmRequest.getPwmApplication().getCrService();
+        crService.clearResponses(
+                pwmRequest.getPwmSession().getLabel(),
+                userIdentity,
+                chaiUser,
+                userGUID
+        );
+
+        // mark the event log
+        {
+            final HelpdeskAuditRecord auditRecord = new AuditRecordFactory(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession()).createHelpdeskAuditRecord(
+                    AuditEvent.HELPDESK_CLEAR_RESPONSES,
+                    pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
+                    null,
+                    userIdentity,
+                    pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress(),
+                    pwmRequest.getPwmSession().getSessionStateBean().getSrcHostname()
+            );
+            pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
         }
 
-        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
-        final MacroMachine macroMachine = new MacroMachine(
-                pwmApplication,
+        final RestResultBean restResultBean = new RestResultBean();
+        restResultBean.setSuccessMessage(Message.getLocalizedMessage(pwmRequest.getLocale(), Message.Success_Unknown, pwmRequest.getConfig()));
+        pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
+    }
+
+    @ActionHandler(action = "checkPassword")
+    private ProcessStatus processCheckPasswordAction(final PwmRequest pwmRequest) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
+    {
+        final RestCheckPasswordServer.JsonInput jsonInput = JsonUtil.deserialize(
+                pwmRequest.readRequestBodyAsString(),
+                RestCheckPasswordServer.JsonInput.class
+        );
+
+        final UserIdentity userIdentity = UserIdentity.fromKey(jsonInput.getUsername(), pwmRequest.getPwmApplication());
+        final HelpdeskProfile helpdeskProfile = pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmRequest.getPwmApplication());
+        final ChaiUser chaiUser = getChaiUser(pwmRequest, getHelpdeskProfile(pwmRequest), userIdentity);
+        final UserInfo userInfo = UserInfoFactory.newUserInfo(
+                pwmRequest.getPwmApplication(),
+                pwmRequest.getSessionLabel(),
+                pwmRequest.getLocale(),
+                userIdentity,
+                chaiUser.getChaiProvider()
+        );
+
+        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+
+        {
+            final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskUIMode.class);
+            if (mode == HelpdeskUIMode.none) {
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SECURITY_VIOLATION,"setting "
+                        + PwmSetting.HELPDESK_CLEAR_RESPONSES.toMenuLocationDebug(helpdeskProfile.getIdentifier(), pwmRequest.getLocale())
+                        + " must not be set to none"));
+            }
+        }
+
+
+        final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword(
+                pwmRequest.getPwmApplication(),
+                pwmRequest.getLocale(),
+                chaiUser,
+                userInfo,
+                null,
+                PasswordData.forStringValue(jsonInput.getPassword1()),
+                PasswordData.forStringValue(jsonInput.getPassword2())
+        );
+
+        final RestCheckPasswordServer.JsonOutput jsonResponse = RestCheckPasswordServer.JsonOutput.fromPasswordCheckInfo(passwordCheckInfo);
+        final RestResultBean restResultBean = new RestResultBean(jsonResponse);
+        pwmRequest.outputJsonResult(restResultBean);
+
+        return ProcessStatus.Halt;
+    }
+
+    @ActionHandler(action = "setPassword")
+    private ProcessStatus processSetPasswordAction(final PwmRequest pwmRequest) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
+    {
+        final RestSetPasswordServer.JsonInputData jsonInput = JsonUtil.deserialize(
+                pwmRequest.readRequestBodyAsString(),
+                RestSetPasswordServer.JsonInputData.class
+        );
+
+        final PasswordData newPassword = new PasswordData(jsonInput.getPassword());
+        final HelpdeskProfile helpdeskProfile = pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmRequest.getPwmApplication());
+        final UserIdentity userIdentity = UserIdentity.fromKey(jsonInput.getUsername(),pwmRequest.getPwmApplication());
+        final ChaiUser chaiUser = getChaiUser(pwmRequest, helpdeskProfile, userIdentity);
+        final UserInfo userInfo = UserInfoFactory.newUserInfo(
+                pwmRequest.getPwmApplication(),
                 pwmRequest.getSessionLabel(),
-                helpdeskDetailInfoBean.getUserInfo(),
-                null
+                pwmRequest.getLocale(),
+                userIdentity,
+                chaiUser.getChaiProvider()
         );
 
-        pwmApplication.getEmailQueue().submitEmail(
-                configuredEmailSetting,
-                helpdeskDetailInfoBean.getUserInfo(),
-                macroMachine
+        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+
+        {
+            final HelpdeskUIMode mode = helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_CLEAR_RESPONSES, HelpdeskUIMode.class);
+            if (mode == HelpdeskUIMode.none) {
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SECURITY_VIOLATION,"setting "
+                        + PwmSetting.HELPDESK_CLEAR_RESPONSES.toMenuLocationDebug(helpdeskProfile.getIdentifier(), pwmRequest.getLocale())
+                        + " must not be set to none"));
+            }
+        }
+
+        try {
+            PasswordUtility.helpdeskSetUserPassword(
+                    pwmRequest.getPwmSession(),
+                    chaiUser,
+                    userIdentity,
+                    pwmRequest.getPwmApplication(),
+                    newPassword
+            );
+        } catch (PwmException e) {
+            LOGGER.error("error during set password REST operation: " + e.getMessage());
+            pwmRequest.outputJsonResult(RestResultBean.fromError(e.getErrorInformation(), pwmRequest));
+            return ProcessStatus.Halt;
+        }
+
+        pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_ChangedHelpdeskPassword, userInfo.getUsername()));
+        return ProcessStatus.Halt;
+    }
+
+    @ActionHandler(action = "randomPassword")
+    private ProcessStatus processRandomPasswordAction(final PwmRequest pwmRequest) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
+    {
+        final RestRandomPasswordServer.JsonInput input = JsonUtil.deserialize(pwmRequest.readRequestBodyAsString(), RestRandomPasswordServer.JsonInput.class);
+        final UserIdentity userIdentity = UserIdentity.fromKey(input.getUsername(), pwmRequest.getPwmApplication());
+
+        final HelpdeskProfile helpdeskProfile = getHelpdeskProfile(pwmRequest);
+
+        HelpdeskServletUtil.checkIfUserIdentityViewable(pwmRequest, helpdeskProfile, userIdentity);
+
+        final ChaiUser chaiUser = getChaiUser(pwmRequest, helpdeskProfile, userIdentity);
+        final UserInfo userInfo = UserInfoFactory.newUserInfo(
+                pwmRequest.getPwmApplication(),
+                pwmRequest.getSessionLabel(),
+                pwmRequest.getLocale(),
+                userIdentity,
+                chaiUser.getChaiProvider()
         );
+
+        final RandomPasswordGenerator.RandomGeneratorConfig.RandomGeneratorConfigBuilder randomConfigBuilder
+                = RandomPasswordGenerator.RandomGeneratorConfig.builder();
+
+        randomConfigBuilder.passwordPolicy(userInfo.getPasswordPolicy());
+
+        final RandomPasswordGenerator.RandomGeneratorConfig randomConfig = randomConfigBuilder.build();
+        final PasswordData randomPassword = RandomPasswordGenerator.createRandomPassword(pwmRequest.getPwmSession().getLabel(), randomConfig, pwmRequest.getPwmApplication());
+        final RestRandomPasswordServer.JsonOutput jsonOutput = new RestRandomPasswordServer.JsonOutput();
+        jsonOutput.setPassword(randomPassword.getStringValue());
+
+        final RestResultBean restResultBean = new RestResultBean(jsonOutput);
+        pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
+    }
+
+    private UserIdentity readUserKeyRequestParameter(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException
+    {
+        final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
+        if (userKey.length() < 1) {
+
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing");
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+        return UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication());
     }
 }

+ 179 - 0
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java

@@ -0,0 +1,179 @@
+package password.pwm.http.servlet.helpdesk;
+
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.bean.EmailItemBean;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.option.IdentityVerificationMethod;
+import password.pwm.config.profile.HelpdeskProfile;
+import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.JspUrl;
+import password.pwm.http.PwmHttpRequestWrapper;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmRequestAttribute;
+import password.pwm.ldap.LdapPermissionTester;
+import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroMachine;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+class HelpdeskServletUtil {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(HelpdeskServletUtil.class);
+
+    static String getSearchFilter(final Configuration configuration, final HelpdeskProfile helpdeskProfile) {
+        final String configuredFilter = helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_SEARCH_FILTER);
+        if (configuredFilter != null && !configuredFilter.isEmpty()) {
+            return configuredFilter;
+        }
+
+        final List<String> defaultObjectClasses = configuration.readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES);
+        final List<FormConfiguration> searchAttributes = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
+        final StringBuilder filter = new StringBuilder();
+        filter.append("(&"); //open AND clause for objectclasses and attributes
+        for (final String objectClass : defaultObjectClasses) {
+            filter.append("(objectClass=").append(objectClass).append(")");
+        }
+        filter.append("(|"); // open OR clause for attributes
+        for (final FormConfiguration formConfiguration : searchAttributes) {
+            if (formConfiguration != null && formConfiguration.getName() != null) {
+                final String searchAttribute = formConfiguration.getName();
+                filter.append("(").append(searchAttribute).append("=*").append(PwmConstants.VALUE_REPLACEMENT_USERNAME).append("*)");
+            }
+        }
+        filter.append(")"); // close OR clause
+        filter.append(")"); // close AND clause
+        return filter.toString();
+    }
+
+    static void checkIfUserIdentityViewable(
+            final PwmRequest pwmRequest,
+            final HelpdeskProfile helpdeskProfile,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException {
+        final String filterSetting = getSearchFilter(pwmRequest.getConfig(), helpdeskProfile);
+        String filterString = filterSetting.replace(PwmConstants.VALUE_REPLACEMENT_USERNAME, "*");
+        while (filterString.contains("**")) {
+            filterString = filterString.replace("**", "*");
+        }
+
+        final boolean match = LdapPermissionTester.testQueryMatch(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userIdentity, filterString);
+        if (!match) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "requested userDN is not available within configured search filter"));
+        }
+    }
+
+    static void processDetailRequest(
+            final PwmRequest pwmRequest,
+            final HelpdeskProfile helpdeskProfile,
+            final UserIdentity userIdentity
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
+    {
+        final UserIdentity actorUserIdentity = pwmRequest.getUserInfoIfLoggedIn().canonicalized(pwmRequest.getPwmApplication());
+
+        if (actorUserIdentity.canonicalEquals(userIdentity, pwmRequest.getPwmApplication())) {
+            final String errorMsg = "cannot select self";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,errorMsg);
+            LOGGER.debug(pwmRequest, errorInformation);
+            pwmRequest.respondWithError(errorInformation, false);
+            return;
+        }
+        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 (!HelpdeskServletUtil.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 = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
+        pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskDetail, helpdeskDetailInfoBean);
+
+        if (helpdeskDetailInfoBean != null && helpdeskDetailInfoBean.getUserInfo() != null) {
+            final String obfuscatedDN = helpdeskDetailInfoBean.getUserInfo().getUserIdentity().toObfuscatedKey(pwmRequest.getPwmApplication());
+            pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskObfuscatedDN, obfuscatedDN);
+            pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskUsername, helpdeskDetailInfoBean.getUserInfo().getUsername());
+        }
+
+        StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_USER_LOOKUP);
+        pwmRequest.setAttribute(PwmRequestAttribute.HelpdeskVerificationEnabled, !helpdeskProfile.readOptionalVerificationMethods().isEmpty());
+        pwmRequest.forwardToJsp(JspUrl.HELPDESK_DETAIL);
+    }
+
+    static UserIdentity userIdentityFromMap(final PwmRequest pwmRequest, final Map<String,String> bodyMap) throws PwmUnrecoverableException {
+        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");
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+
+        return UserIdentity.fromObfuscatedKey(userKey, pwmRequest.getPwmApplication());
+    }
+
+
+    static 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 true;
+            }
+        }
+        return false;
+    }
+
+    static void sendUnlockNoticeEmail(
+            final PwmRequest pwmRequest,
+            final HelpdeskProfile helpdeskProfile,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final Configuration config = pwmRequest.getConfig();
+        final Locale locale = pwmRequest.getLocale();
+        final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_HELPDESK_UNLOCK, locale);
+
+        if (configuredEmailSetting == null) {
+            LOGGER.debug(pwmRequest, "skipping send helpdesk unlock notice email for '" + userIdentity + "' no email configured");
+            return;
+        }
+
+        final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo(pwmRequest, helpdeskProfile, userIdentity);
+        final MacroMachine macroMachine = new MacroMachine(
+                pwmApplication,
+                pwmRequest.getSessionLabel(),
+                helpdeskDetailInfoBean.getUserInfo(),
+                null
+        );
+
+        pwmApplication.getEmailQueue().submitEmail(
+                configuredEmailSetting,
+                helpdeskDetailInfoBean.getUserInfo(),
+                macroMachine
+        );
+    }
+
+}

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -349,7 +349,7 @@ public class NewUserServlet extends ControlledPwmServlet {
                         passwordCheckInfo.getErrorCode()
                 );
             }
-            final RestCheckPasswordServer.JsonData jsonData = RestCheckPasswordServer.JsonData.fromPasswordCheckInfo(
+            final RestCheckPasswordServer.JsonOutput jsonData = RestCheckPasswordServer.JsonOutput.fromPasswordCheckInfo(
                     passwordCheckInfo);
 
             final RestResultBean restResultBean = new RestResultBean();

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

@@ -160,9 +160,10 @@ public class RestResultBean implements Serializable {
 
     public static RestResultBean forSuccessMessage(
             final PwmRequest pwmRequest,
-            final Message message
+            final Message message,
+            final String... fieldValues
     ) {
-        return forSuccessMessage(pwmRequest.getLocale(), pwmRequest.getConfig(), message);
+        return forSuccessMessage(pwmRequest.getLocale(), pwmRequest.getConfig(), message, fieldValues);
     }
 
     public static RestResultBean forConfirmMessage(

+ 10 - 6
server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java

@@ -77,7 +77,11 @@ public class RestCheckPasswordServer extends AbstractRestServer {
         public String username;
     }
 
-    public static class JsonData implements Serializable
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class JsonOutput implements Serializable
     {
         public int version;
         public int strength;
@@ -86,10 +90,10 @@ public class RestCheckPasswordServer extends AbstractRestServer {
         public boolean passed;
         public int errorCode;
 
-        public static JsonData fromPasswordCheckInfo(
+        public static JsonOutput fromPasswordCheckInfo(
                 final PasswordUtility.PasswordCheckInfo checkInfo
         ) {
-            final JsonData outputMap = new JsonData();
+            final JsonOutput outputMap = new JsonOutput();
             outputMap.version = 2;
             outputMap.strength = checkInfo.getStrength();
             outputMap.match = checkInfo.getMatch();
@@ -177,7 +181,7 @@ public class RestCheckPasswordServer extends AbstractRestServer {
                 restRequestBean.getPwmApplication().getStatisticsManager().incrementValue(Statistic.PASSWORD_RULE_CHECKS);
             }
 
-            final JsonData jsonData = doPasswordRuleCheck(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), checkRequest);
+            final JsonOutput jsonData = doPasswordRuleCheck(restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), checkRequest);
             final RestResultBean restResultBean = new RestResultBean();
             restResultBean.setData(jsonData);
             final TimeDuration timeDuration = TimeDuration.fromCurrent(startTime);
@@ -230,7 +234,7 @@ public class RestCheckPasswordServer extends AbstractRestServer {
     }
 
 
-    public static JsonData doPasswordRuleCheck(
+    public static JsonOutput doPasswordRuleCheck(
             final PwmApplication pwmApplication,
             final PwmSession pwmSession,
             final PasswordCheckRequest checkRequest
@@ -258,6 +262,6 @@ public class RestCheckPasswordServer extends AbstractRestServer {
                 checkRequest.getPassword2()
         );
 
-        return JsonData.fromPasswordCheckInfo(passwordCheckInfo);
+        return JsonOutput.fromPasswordCheckInfo(passwordCheckInfo);
     }
 }

+ 16 - 7
server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java

@@ -24,6 +24,9 @@ package password.pwm.ws.server.rest;
 
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
@@ -68,19 +71,25 @@ public class RestRandomPasswordServer extends AbstractRestServer {
             .publicDuringConfig(true)
             .build();
 
+    @Getter
+    @Setter
+    @NoArgsConstructor
     public static class JsonOutput implements Serializable
     {
-        public String password;
+        private String password;
     }
 
+    @Getter
+    @Setter
+    @NoArgsConstructor
     public static class JsonInput implements Serializable
     {
-        public String username;
-        public int strength;
-        public int minLength;
-        public int maxLength;
-        public String chars;
-        public boolean noUser;
+        private String username;
+        private int strength;
+        private int minLength;
+        private int maxLength;
+        private String chars;
+        private boolean noUser;
     }
 
     @POST

+ 4 - 0
server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java

@@ -23,6 +23,8 @@
 package password.pwm.ws.server.rest;
 
 import com.novell.ldapchai.ChaiUser;
+import lombok.Getter;
+import lombok.Setter;
 import password.pwm.Permission;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.HelpdeskProfile;
@@ -60,6 +62,8 @@ public class RestSetPasswordServer extends AbstractRestServer {
 
     public static final PwmLogger LOGGER = PwmLogger.forClass(RestSetPasswordServer.class);
 
+    @Getter
+    @Setter
     public static class JsonInputData implements Serializable
     {
         public String username;

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

@@ -158,7 +158,7 @@ Success_CreateGuest=The new guest account has been successfully created.  The gu
 Success_CreateUser=Your new user account has been successfully created.
 Success_NewUserForm=Your account is ready to be created.  Continue when ready.
 Success_PasswordChange=The password has been changed successfully.
-Success_ChangedHelpdeskPassword=The password has been changed successfully for user 
+Success_ChangedHelpdeskPassword=The password has been changed successfully for user %1%. 
 Success_PasswordReset=Password for %1% has been set successfully.
 Success_PasswordSend=Your new password has been sent to %1%.  Please close this window and then login using your new password.
 Success_ResponsesMeetRules=Your answers meet the requirements.  Click Save Answers when ready.

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

@@ -514,7 +514,7 @@
                         <% } %>
                     </div>
                     <br/>
-                    <div class="footnote"><div class="timestamp"><%=JavaHelper.toIsoDate(Instant.now())%></div></div>
+                    <div class="footnote"><span class="timestamp"><%=JavaHelper.toIsoDate(Instant.now())%></span></div>
                 </td>
                 <td class="noborder" style="width: 200px; max-width:200px; text-align: left; vertical-align: top">
                     <div class="noborder" style="margin-top: 25px; margin-left: 5px">

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

@@ -328,7 +328,7 @@ PWM_CHANGEPW.fetchRandoms=function(randomConfig) {
             PWM_CHANGEPW.fetchRandoms(randomConfig);
         };
 
-        var url = PWM_GLOBAL['url-restservice'] + "/randompassword";
+        var url = PWM_MAIN.addParamToUrl(window.location.pathname, 'processAction','randomPassword');
         var content = randomConfig['dataInput'] == null ? { } : randomConfig['dataInput'];
 
         PWM_MAIN.ajaxRequest(url,successFunction,{content:content});

+ 5 - 6
server/src/main/webapp/public/resources/js/helpdesk.js

@@ -55,11 +55,10 @@ PWM_HELPDESK.executeAction = function(actionName) {
 };
 
 PWM_HELPDESK.doResponseClear = function() {
-    var username = PWM_VAR['helpdesk_obfuscatedDN'];
     PWM_MAIN.showWaitDialog({loadFunction:function() {
-        var url = PWM_GLOBAL['url-restservice'] + "/challenges?username=" + username;
+        var url = "helpdesk?processAction=clearResponses&userKey=" + PWM_VAR['helpdesk_obfuscatedDN'];
         var loadFunction = function(results) {
-            if (results['error'] != true) {
+            if (results['error'] !== true) {
                 PWM_MAIN.showDialog({
                     title: PWM_MAIN.showString('Button_ClearResponses'),
                     text: results['successMessage'],
@@ -71,7 +70,7 @@ PWM_HELPDESK.doResponseClear = function() {
                 PWM_MAIN.showErrorDialog(results);
             }
         };
-        PWM_MAIN.ajaxRequest(url,loadFunction,{method:'DELETE'});
+        PWM_MAIN.ajaxRequest(url,loadFunction);
     }});
 };
 
@@ -84,7 +83,7 @@ PWM_HELPDESK.doPasswordChange = function(password, random) {
         inputValues['password'] = password;
     }
     PWM_MAIN.showWaitDialog({loadFunction:function() {
-        var url = PWM_GLOBAL['url-restservice'] + "/setpassword";
+        var url = "helpdesk?processAction=setPassword";
         var loadFunction = function(results) {
             var bodyText = "";
             if (results['error'] == true) {
@@ -399,7 +398,7 @@ PWM_HELPDESK.deleteUser = function() {
     PWM_MAIN.showConfirmDialog({
         text:PWM_MAIN.showString('Confirm_DeleteUser'),
         okAction:function(){
-            var url = "Helpdesk?processAction=deleteUser&userKey=" + PWM_VAR['helpdesk_obfuscatedDN'];
+            var url = "helpdesk?processAction=deleteUser&userKey=" + PWM_VAR['helpdesk_obfuscatedDN'];
             var loadFunction = function(data) {
                 PWM_MAIN.closeWaitDialog();
                 if (data['error'] == true) {